Switch from MySQL-python to PyMySQL
As discussed in the Liberty Design Summit "Moving apps to Python 3" cross-project workshop, the way forward in the near future is to switch to the pure-python PyMySQL library as a default. Added a special test environment to keep MySQL-python support. Documentation modified. https://etherpad.openstack.org/p/liberty-cross-project-python3 Change-Id: I12b32dc097a121bd43991bc38dd4d289b65e86c1
This commit is contained in:
parent
910d40aa39
commit
9b552046f5
@ -26,12 +26,15 @@ How to run unit tests
|
|||||||
oslo.db (as all OpenStack projects) uses tox to run unit tests. You can find
|
oslo.db (as all OpenStack projects) uses tox to run unit tests. You can find
|
||||||
general information about OpenStack unit tests and testing with tox in wiki_.
|
general information about OpenStack unit tests and testing with tox in wiki_.
|
||||||
|
|
||||||
oslo.db tests use MySQL-python as the default MySQL DB API driver (which is
|
oslo.db tests use PyMySQL as the default MySQL DB API driver (which is true for
|
||||||
true for OpenStack), and psycopg2 for PostgreSQL. pip will build these libs in
|
OpenStack), and psycopg2 for PostgreSQL. pip will build these libs in your
|
||||||
your venv, so you must ensure that you have the required system packages
|
venv, so you must ensure that you have the required system packages installed
|
||||||
installed. For Ubuntu/Debian they are python-dev, libmysqlclient-dev and
|
for psycopg2 (PyMySQL is a pure-Python implementation and so needs no
|
||||||
libpq-dev. For Fedora/CentOS - gcc, python-devel, postgresql-devel and
|
additional system packages). For Ubuntu/Debian they are python-dev, and
|
||||||
mysql-devel.
|
libpq-dev. For Fedora/CentOS - gcc, python-devel and postgresql-devel.
|
||||||
|
There is also a separate env for testing with MySQL-python. If you are suppose
|
||||||
|
to run these tests as well, you need to install libmysqlclient-dev on Ubuntu/Debian
|
||||||
|
or mysql-devel for Fedora/CentOS.
|
||||||
|
|
||||||
The oslo.db unit tests system allows to run unittests on real databases. At the
|
The oslo.db unit tests system allows to run unittests on real databases. At the
|
||||||
moment it supports MySQL, PostgreSQL and SQLite.
|
moment it supports MySQL, PostgreSQL and SQLite.
|
||||||
|
@ -8,16 +8,37 @@ At the command line::
|
|||||||
|
|
||||||
You will also need to install at least one SQL backend::
|
You will also need to install at least one SQL backend::
|
||||||
|
|
||||||
$ pip install MySQL-python
|
$ pip install psycopg2
|
||||||
|
|
||||||
|
Or::
|
||||||
|
|
||||||
|
$ pip install PyMySQL
|
||||||
|
|
||||||
Or::
|
Or::
|
||||||
|
|
||||||
$ pip install pysqlite
|
$ pip install pysqlite
|
||||||
|
|
||||||
Using with MySQL
|
|
||||||
----------------
|
|
||||||
|
|
||||||
If using MySQL make sure to install the MySQL client development package for
|
Using with PostgreSQL
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
If you are using PostgreSQL make sure to install the PostgreSQL client
|
||||||
|
development package for your distro. On Ubuntu this is done as follows::
|
||||||
|
|
||||||
|
$ sudo apt-get install libpq-dev
|
||||||
|
$ pip install psycopg2
|
||||||
|
|
||||||
|
The installation of psycopg2 will fail if libpq-dev is not installed first.
|
||||||
|
Note that even in a virtual environment the libpq-dev will be installed
|
||||||
|
system wide.
|
||||||
|
|
||||||
|
|
||||||
|
Using with MySQL-python
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
PyMySQL is a default MySQL DB API driver for oslo.db, as well as for the whole
|
||||||
|
OpenStack. But you still can use MySQL-python as an alternative DB API driver.
|
||||||
|
For MySQL-python you must install the MySQL client development package for
|
||||||
your distro. On Ubuntu this is done as follows::
|
your distro. On Ubuntu this is done as follows::
|
||||||
|
|
||||||
$ sudo apt-get install libmysqlclient-dev
|
$ sudo apt-get install libmysqlclient-dev
|
||||||
|
@ -455,7 +455,7 @@ class MySQLBackendImpl(BackendImpl):
|
|||||||
default_engine_kwargs = {'mysql_sql_mode': 'TRADITIONAL'}
|
default_engine_kwargs = {'mysql_sql_mode': 'TRADITIONAL'}
|
||||||
|
|
||||||
def create_opportunistic_driver_url(self):
|
def create_opportunistic_driver_url(self):
|
||||||
return "mysql://openstack_citest:openstack_citest@localhost/"
|
return "mysql+pymysql://openstack_citest:openstack_citest@localhost/"
|
||||||
|
|
||||||
def create_named_database(self, engine, ident, conditional=False):
|
def create_named_database(self, engine, ident, conditional=False):
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
|
@ -46,7 +46,16 @@ class _SQLAExceptionMatcher(object):
|
|||||||
self.assertTrue(issubclass(exc.__class__, exception_type))
|
self.assertTrue(issubclass(exc.__class__, exception_type))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(exc.__class__.__name__, exception_type)
|
self.assertEqual(exc.__class__.__name__, exception_type)
|
||||||
self.assertEqual(str(exc.orig).lower(), message.lower())
|
if isinstance(message, tuple):
|
||||||
|
self.assertEqual(
|
||||||
|
[a.lower()
|
||||||
|
if isinstance(a, six.string_types) else a
|
||||||
|
for a in exc.orig.args],
|
||||||
|
[m.lower()
|
||||||
|
if isinstance(m, six.string_types) else m for m in message]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertEqual(str(exc.orig).lower(), message.lower())
|
||||||
if sql is not None:
|
if sql is not None:
|
||||||
self.assertEqual(exc.statement, sql)
|
self.assertEqual(exc.statement, sql)
|
||||||
if params is not None:
|
if params is not None:
|
||||||
@ -359,10 +368,10 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
|||||||
self.assertInnerException(
|
self.assertInnerException(
|
||||||
matched,
|
matched,
|
||||||
"IntegrityError",
|
"IntegrityError",
|
||||||
"(1452, 'Cannot add or update a child row: a "
|
(1452, "Cannot add or update a child row: a "
|
||||||
"foreign key constraint fails (`{0}`.`resource_entity`, "
|
"foreign key constraint fails (`{0}`.`resource_entity`, "
|
||||||
"CONSTRAINT `foo_fkey` FOREIGN KEY (`foo_id`) REFERENCES "
|
"CONSTRAINT `foo_fkey` FOREIGN KEY (`foo_id`) REFERENCES "
|
||||||
"`resource_foo` (`id`))')".format(self.engine.url.database),
|
"`resource_foo` (`id`))".format(self.engine.url.database)),
|
||||||
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
||||||
(1, 2)
|
(1, 2)
|
||||||
)
|
)
|
||||||
@ -382,10 +391,13 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
|||||||
self.assertInnerException(
|
self.assertInnerException(
|
||||||
matched,
|
matched,
|
||||||
"IntegrityError",
|
"IntegrityError",
|
||||||
'(1452, \'Cannot add or update a child row: a '
|
(
|
||||||
'foreign key constraint fails ("{0}"."resource_entity", '
|
1452,
|
||||||
'CONSTRAINT "foo_fkey" FOREIGN KEY ("foo_id") REFERENCES '
|
'Cannot add or update a child row: a '
|
||||||
'"resource_foo" ("id"))\')'.format(self.engine.url.database),
|
'foreign key constraint fails ("{0}"."resource_entity", '
|
||||||
|
'CONSTRAINT "foo_fkey" FOREIGN KEY ("foo_id") REFERENCES '
|
||||||
|
'"resource_foo" ("id"))'.format(self.engine.url.database)
|
||||||
|
),
|
||||||
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
||||||
(1, 2)
|
(1, 2)
|
||||||
)
|
)
|
||||||
@ -436,7 +448,7 @@ class TestDuplicate(TestsExceptionFilter):
|
|||||||
"PRIMARY KEY must be unique 'insert into t values(10)'",
|
"PRIMARY KEY must be unique 'insert into t values(10)'",
|
||||||
expected_columns=[])
|
expected_columns=[])
|
||||||
|
|
||||||
def test_mysql_mysqldb(self):
|
def test_mysql_pymysql(self):
|
||||||
self._run_dupe_constraint_test(
|
self._run_dupe_constraint_test(
|
||||||
"mysql",
|
"mysql",
|
||||||
'(1062, "Duplicate entry '
|
'(1062, "Duplicate entry '
|
||||||
@ -541,7 +553,7 @@ class TestDeadlock(TestsExceptionFilter):
|
|||||||
|
|
||||||
self.assertEqual(matched.orig.__class__.__name__, expected_dbapi_cls)
|
self.assertEqual(matched.orig.__class__.__name__, expected_dbapi_cls)
|
||||||
|
|
||||||
def test_mysql_mysqldb_deadlock(self):
|
def test_mysql_pymysql_deadlock(self):
|
||||||
self._run_deadlock_detect_test(
|
self._run_deadlock_detect_test(
|
||||||
"mysql",
|
"mysql",
|
||||||
"(1213, 'Deadlock found when trying "
|
"(1213, 'Deadlock found when trying "
|
||||||
@ -549,7 +561,7 @@ class TestDeadlock(TestsExceptionFilter):
|
|||||||
"transaction')"
|
"transaction')"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_mysql_mysqldb_galera_deadlock(self):
|
def test_mysql_pymysql_galera_deadlock(self):
|
||||||
self._run_deadlock_detect_test(
|
self._run_deadlock_detect_test(
|
||||||
"mysql",
|
"mysql",
|
||||||
"(1205, 'Lock wait timeout exceeded; "
|
"(1205, 'Lock wait timeout exceeded; "
|
||||||
|
@ -793,8 +793,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher = orig = utils.dispatch_for_dialect("*")(
|
dispatcher = orig = utils.dispatch_for_dialect("*")(
|
||||||
callable_fn.default)
|
callable_fn.default)
|
||||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||||
callable_fn.mysql_mysqldb)
|
callable_fn.mysql_pymysql)
|
||||||
|
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||||
|
callable_fn.mysql)
|
||||||
dispatcher = dispatcher.dispatch_for("postgresql")(
|
dispatcher = dispatcher.dispatch_for("postgresql")(
|
||||||
callable_fn.postgresql)
|
callable_fn.postgresql)
|
||||||
|
|
||||||
@ -808,7 +810,8 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
for targ in [
|
for targ in [
|
||||||
callable_fn.default,
|
callable_fn.default,
|
||||||
callable_fn.sqlite,
|
callable_fn.sqlite,
|
||||||
callable_fn.mysql_mysqldb,
|
callable_fn.mysql,
|
||||||
|
callable_fn.mysql_pymysql,
|
||||||
callable_fn.postgresql,
|
callable_fn.postgresql,
|
||||||
callable_fn.postgresql_psycopg2,
|
callable_fn.postgresql_psycopg2,
|
||||||
callable_fn.pyodbc
|
callable_fn.pyodbc
|
||||||
@ -818,8 +821,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher = orig = utils.dispatch_for_dialect("*", multiple=True)(
|
dispatcher = orig = utils.dispatch_for_dialect("*", multiple=True)(
|
||||||
callable_fn.default)
|
callable_fn.default)
|
||||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||||
callable_fn.mysql_mysqldb)
|
callable_fn.mysql_pymysql)
|
||||||
|
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||||
|
callable_fn.mysql)
|
||||||
dispatcher = dispatcher.dispatch_for("postgresql+*")(
|
dispatcher = dispatcher.dispatch_for("postgresql+*")(
|
||||||
callable_fn.postgresql)
|
callable_fn.postgresql)
|
||||||
dispatcher = dispatcher.dispatch_for("postgresql+psycopg2")(
|
dispatcher = dispatcher.dispatch_for("postgresql+psycopg2")(
|
||||||
@ -836,15 +841,17 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher, callable_fn = self._single_fixture()
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
dispatcher("sqlite://", 1)
|
dispatcher("sqlite://", 1)
|
||||||
dispatcher("postgresql+psycopg2://u:p@h/t", 2)
|
dispatcher("postgresql+psycopg2://u:p@h/t", 2)
|
||||||
dispatcher("mysql://u:p@h/t", 3)
|
dispatcher("mysql+pymysql://u:p@h/t", 3)
|
||||||
dispatcher("mysql+mysqlconnector://u:p@h/t", 4)
|
dispatcher("mysql://u:p@h/t", 4)
|
||||||
|
dispatcher("mysql+mysqlconnector://u:p@h/t", 5)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[
|
[
|
||||||
mock.call.sqlite('sqlite://', 1),
|
mock.call.sqlite('sqlite://', 1),
|
||||||
mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2),
|
mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2),
|
||||||
mock.call.mysql_mysqldb("mysql://u:p@h/t", 3),
|
mock.call.mysql_pymysql("mysql+pymysql://u:p@h/t", 3),
|
||||||
mock.call.default("mysql+mysqlconnector://u:p@h/t", 4)
|
mock.call.mysql("mysql://u:p@h/t", 4),
|
||||||
|
mock.call.mysql("mysql+mysqlconnector://u:p@h/t", 5),
|
||||||
],
|
],
|
||||||
callable_fn.mock_calls)
|
callable_fn.mock_calls)
|
||||||
|
|
||||||
@ -962,10 +969,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
|
|
||||||
def test_single_retval(self):
|
def test_single_retval(self):
|
||||||
dispatcher, callable_fn = self._single_fixture()
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
callable_fn.mysql_mysqldb.return_value = 5
|
callable_fn.mysql_pymysql.return_value = 5
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
dispatcher("mysql://u:p@h/t", 3), 5
|
dispatcher("mysql+pymysql://u:p@h/t", 3), 5
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_engine(self):
|
def test_engine(self):
|
||||||
@ -978,14 +985,25 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
callable_fn.mock_calls
|
callable_fn.mock_calls
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_url(self):
|
def test_url_pymysql(self):
|
||||||
url = sqlalchemy.engine.url.make_url(
|
url = sqlalchemy.engine.url.make_url(
|
||||||
"mysql+mysqldb://scott:tiger@localhost/test")
|
"mysql+pymysql://scott:tiger@localhost/test")
|
||||||
dispatcher, callable_fn = self._single_fixture()
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
|
|
||||||
dispatcher(url, 15)
|
dispatcher(url, 15)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[mock.call.mysql_mysqldb(url, 15)],
|
[mock.call.mysql_pymysql(url, 15)],
|
||||||
|
callable_fn.mock_calls
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_url_mysql_generic(self):
|
||||||
|
url = sqlalchemy.engine.url.make_url(
|
||||||
|
"mysql://scott:tiger@localhost/test")
|
||||||
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
|
|
||||||
|
dispatcher(url, 15)
|
||||||
|
self.assertEqual(
|
||||||
|
[mock.call.mysql(url, 15)],
|
||||||
callable_fn.mock_calls
|
callable_fn.mock_calls
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1030,9 +1048,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher, callable_fn = self._multiple_fixture()
|
dispatcher, callable_fn = self._multiple_fixture()
|
||||||
|
|
||||||
dispatcher("postgresql+pyodbc://", 1)
|
dispatcher("postgresql+pyodbc://", 1)
|
||||||
dispatcher("mysql://", 2)
|
dispatcher("mysql+pymysql://", 2)
|
||||||
dispatcher("ibm_db_sa+db2://", 3)
|
dispatcher("ibm_db_sa+db2://", 3)
|
||||||
dispatcher("postgresql+psycopg2://", 4)
|
dispatcher("postgresql+psycopg2://", 4)
|
||||||
|
dispatcher("postgresql://", 5)
|
||||||
|
|
||||||
# TODO(zzzeek): there is a deterministic order here, but we might
|
# TODO(zzzeek): there is a deterministic order here, but we might
|
||||||
# want to tweak it, or maybe provide options. default first?
|
# want to tweak it, or maybe provide options. default first?
|
||||||
@ -1042,12 +1061,18 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
mock.call.postgresql('postgresql+pyodbc://', 1),
|
mock.call.postgresql('postgresql+pyodbc://', 1),
|
||||||
mock.call.pyodbc('postgresql+pyodbc://', 1),
|
mock.call.pyodbc('postgresql+pyodbc://', 1),
|
||||||
mock.call.default('postgresql+pyodbc://', 1),
|
mock.call.default('postgresql+pyodbc://', 1),
|
||||||
mock.call.mysql_mysqldb('mysql://', 2),
|
mock.call.mysql_pymysql('mysql+pymysql://', 2),
|
||||||
mock.call.default('mysql://', 2),
|
mock.call.mysql('mysql+pymysql://', 2),
|
||||||
|
mock.call.default('mysql+pymysql://', 2),
|
||||||
mock.call.default('ibm_db_sa+db2://', 3),
|
mock.call.default('ibm_db_sa+db2://', 3),
|
||||||
mock.call.postgresql_psycopg2('postgresql+psycopg2://', 4),
|
mock.call.postgresql_psycopg2('postgresql+psycopg2://', 4),
|
||||||
mock.call.postgresql('postgresql+psycopg2://', 4),
|
mock.call.postgresql('postgresql+psycopg2://', 4),
|
||||||
mock.call.default('postgresql+psycopg2://', 4),
|
mock.call.default('postgresql+psycopg2://', 4),
|
||||||
|
# note this is called because we resolve the default
|
||||||
|
# DBAPI for the url
|
||||||
|
mock.call.postgresql_psycopg2('postgresql://', 5),
|
||||||
|
mock.call.postgresql('postgresql://', 5),
|
||||||
|
mock.call.default('postgresql://', 5),
|
||||||
],
|
],
|
||||||
callable_fn.mock_calls
|
callable_fn.mock_calls
|
||||||
)
|
)
|
||||||
|
@ -46,7 +46,16 @@ class _SQLAExceptionMatcher(object):
|
|||||||
self.assertTrue(issubclass(exc.__class__, exception_type))
|
self.assertTrue(issubclass(exc.__class__, exception_type))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(exc.__class__.__name__, exception_type)
|
self.assertEqual(exc.__class__.__name__, exception_type)
|
||||||
self.assertEqual(str(exc.orig).lower(), message.lower())
|
if isinstance(message, tuple):
|
||||||
|
self.assertEqual(
|
||||||
|
[a.lower()
|
||||||
|
if isinstance(a, six.string_types) else a
|
||||||
|
for a in exc.orig.args],
|
||||||
|
[m.lower()
|
||||||
|
if isinstance(m, six.string_types) else m for m in message]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertEqual(str(exc.orig).lower(), message.lower())
|
||||||
if sql is not None:
|
if sql is not None:
|
||||||
self.assertEqual(exc.statement, sql)
|
self.assertEqual(exc.statement, sql)
|
||||||
if params is not None:
|
if params is not None:
|
||||||
@ -362,10 +371,10 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
|||||||
self.assertInnerException(
|
self.assertInnerException(
|
||||||
matched,
|
matched,
|
||||||
"IntegrityError",
|
"IntegrityError",
|
||||||
"(1452, 'Cannot add or update a child row: a "
|
(1452, "Cannot add or update a child row: a "
|
||||||
"foreign key constraint fails (`{0}`.`resource_entity`, "
|
"foreign key constraint fails (`{0}`.`resource_entity`, "
|
||||||
"CONSTRAINT `foo_fkey` FOREIGN KEY (`foo_id`) REFERENCES "
|
"CONSTRAINT `foo_fkey` FOREIGN KEY (`foo_id`) REFERENCES "
|
||||||
"`resource_foo` (`id`))')".format(self.engine.url.database),
|
"`resource_foo` (`id`))".format(self.engine.url.database)),
|
||||||
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
||||||
(1, 2)
|
(1, 2)
|
||||||
)
|
)
|
||||||
@ -390,10 +399,13 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
|||||||
self.assertInnerException(
|
self.assertInnerException(
|
||||||
matched,
|
matched,
|
||||||
"IntegrityError",
|
"IntegrityError",
|
||||||
'(1452, \'Cannot add or update a child row: a '
|
(
|
||||||
'foreign key constraint fails ("{0}"."resource_entity", '
|
1452,
|
||||||
'CONSTRAINT "foo_fkey" FOREIGN KEY ("foo_id") REFERENCES '
|
'Cannot add or update a child row: a '
|
||||||
'"resource_foo" ("id"))\')'.format(self.engine.url.database),
|
'foreign key constraint fails ("{0}"."resource_entity", '
|
||||||
|
'CONSTRAINT "foo_fkey" FOREIGN KEY ("foo_id") REFERENCES '
|
||||||
|
'"resource_foo" ("id"))'.format(self.engine.url.database)
|
||||||
|
),
|
||||||
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
"INSERT INTO resource_entity (id, foo_id) VALUES (%s, %s)",
|
||||||
(1, 2)
|
(1, 2)
|
||||||
)
|
)
|
||||||
@ -414,11 +426,14 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
|||||||
self.assertInnerException(
|
self.assertInnerException(
|
||||||
matched,
|
matched,
|
||||||
"IntegrityError",
|
"IntegrityError",
|
||||||
"(1451, 'cannot delete or update a parent row: a foreign key "
|
(
|
||||||
"constraint fails (`{0}`.`resource_entity`, "
|
1451,
|
||||||
"constraint `foo_fkey` "
|
"Cannot delete or update a parent row: a foreign key "
|
||||||
"foreign key (`foo_id`) references "
|
"constraint fails (`{0}`.`resource_entity`, "
|
||||||
"`resource_foo` (`id`))')".format(self.engine.url.database),
|
"constraint `foo_fkey` "
|
||||||
|
"foreign key (`foo_id`) references "
|
||||||
|
"`resource_foo` (`id`))".format(self.engine.url.database)
|
||||||
|
),
|
||||||
"DELETE FROM resource_foo",
|
"DELETE FROM resource_foo",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
@ -483,7 +498,7 @@ class TestDuplicate(TestsExceptionFilter):
|
|||||||
"PRIMARY KEY must be unique 'insert into t values(10)'",
|
"PRIMARY KEY must be unique 'insert into t values(10)'",
|
||||||
expected_columns=[])
|
expected_columns=[])
|
||||||
|
|
||||||
def test_mysql_mysqldb(self):
|
def test_mysql_pymysql(self):
|
||||||
self._run_dupe_constraint_test(
|
self._run_dupe_constraint_test(
|
||||||
"mysql",
|
"mysql",
|
||||||
'(1062, "Duplicate entry '
|
'(1062, "Duplicate entry '
|
||||||
@ -606,7 +621,7 @@ class TestDeadlock(TestsExceptionFilter):
|
|||||||
|
|
||||||
self.assertEqual(matched.orig.__class__.__name__, expected_dbapi_cls)
|
self.assertEqual(matched.orig.__class__.__name__, expected_dbapi_cls)
|
||||||
|
|
||||||
def test_mysql_mysqldb_deadlock(self):
|
def test_mysql_pymysql_deadlock(self):
|
||||||
self._run_deadlock_detect_test(
|
self._run_deadlock_detect_test(
|
||||||
"mysql",
|
"mysql",
|
||||||
"(1213, 'Deadlock found when trying "
|
"(1213, 'Deadlock found when trying "
|
||||||
@ -614,7 +629,7 @@ class TestDeadlock(TestsExceptionFilter):
|
|||||||
"transaction')"
|
"transaction')"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_mysql_mysqldb_galera_deadlock(self):
|
def test_mysql_pymysql_galera_deadlock(self):
|
||||||
self._run_deadlock_detect_test(
|
self._run_deadlock_detect_test(
|
||||||
"mysql",
|
"mysql",
|
||||||
"(1205, 'Lock wait timeout exceeded; "
|
"(1205, 'Lock wait timeout exceeded; "
|
||||||
|
@ -558,7 +558,7 @@ class CreateEngineTest(oslo_test.BaseTestCase):
|
|||||||
|
|
||||||
def test_queuepool_args(self):
|
def test_queuepool_args(self):
|
||||||
engines._init_connection_args(
|
engines._init_connection_args(
|
||||||
url.make_url("mysql://u:p@host/test"), self.args,
|
url.make_url("mysql+pymysql://u:p@host/test"), self.args,
|
||||||
max_pool_size=10, max_overflow=10)
|
max_pool_size=10, max_overflow=10)
|
||||||
self.assertEqual(self.args['pool_size'], 10)
|
self.assertEqual(self.args['pool_size'], 10)
|
||||||
self.assertEqual(self.args['max_overflow'], 10)
|
self.assertEqual(self.args['max_overflow'], 10)
|
||||||
@ -609,6 +609,12 @@ class CreateEngineTest(oslo_test.BaseTestCase):
|
|||||||
self.assertEqual(self.args['connect_args'],
|
self.assertEqual(self.args['connect_args'],
|
||||||
{'charset': 'utf8', 'use_unicode': 0})
|
{'charset': 'utf8', 'use_unicode': 0})
|
||||||
|
|
||||||
|
def test_mysql_pymysql_connect_args_default(self):
|
||||||
|
engines._init_connection_args(
|
||||||
|
url.make_url("mysql+pymysql://u:p@host/test"), self.args)
|
||||||
|
self.assertEqual(self.args['connect_args'],
|
||||||
|
{'charset': 'utf8'})
|
||||||
|
|
||||||
def test_mysql_mysqldb_connect_args_default(self):
|
def test_mysql_mysqldb_connect_args_default(self):
|
||||||
engines._init_connection_args(
|
engines._init_connection_args(
|
||||||
url.make_url("mysql+mysqldb://u:p@host/test"), self.args)
|
url.make_url("mysql+mysqldb://u:p@host/test"), self.args)
|
||||||
|
@ -912,8 +912,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher = orig = utils.dispatch_for_dialect("*")(
|
dispatcher = orig = utils.dispatch_for_dialect("*")(
|
||||||
callable_fn.default)
|
callable_fn.default)
|
||||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||||
callable_fn.mysql_mysqldb)
|
callable_fn.mysql_pymysql)
|
||||||
|
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||||
|
callable_fn.mysql)
|
||||||
dispatcher = dispatcher.dispatch_for("postgresql")(
|
dispatcher = dispatcher.dispatch_for("postgresql")(
|
||||||
callable_fn.postgresql)
|
callable_fn.postgresql)
|
||||||
|
|
||||||
@ -927,7 +929,8 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
for targ in [
|
for targ in [
|
||||||
callable_fn.default,
|
callable_fn.default,
|
||||||
callable_fn.sqlite,
|
callable_fn.sqlite,
|
||||||
callable_fn.mysql_mysqldb,
|
callable_fn.mysql,
|
||||||
|
callable_fn.mysql_pymysql,
|
||||||
callable_fn.postgresql,
|
callable_fn.postgresql,
|
||||||
callable_fn.postgresql_psycopg2,
|
callable_fn.postgresql_psycopg2,
|
||||||
callable_fn.pyodbc
|
callable_fn.pyodbc
|
||||||
@ -937,8 +940,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher = orig = utils.dispatch_for_dialect("*", multiple=True)(
|
dispatcher = orig = utils.dispatch_for_dialect("*", multiple=True)(
|
||||||
callable_fn.default)
|
callable_fn.default)
|
||||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||||
callable_fn.mysql_mysqldb)
|
callable_fn.mysql_pymysql)
|
||||||
|
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||||
|
callable_fn.mysql)
|
||||||
dispatcher = dispatcher.dispatch_for("postgresql+*")(
|
dispatcher = dispatcher.dispatch_for("postgresql+*")(
|
||||||
callable_fn.postgresql)
|
callable_fn.postgresql)
|
||||||
dispatcher = dispatcher.dispatch_for("postgresql+psycopg2")(
|
dispatcher = dispatcher.dispatch_for("postgresql+psycopg2")(
|
||||||
@ -955,15 +960,17 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher, callable_fn = self._single_fixture()
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
dispatcher("sqlite://", 1)
|
dispatcher("sqlite://", 1)
|
||||||
dispatcher("postgresql+psycopg2://u:p@h/t", 2)
|
dispatcher("postgresql+psycopg2://u:p@h/t", 2)
|
||||||
dispatcher("mysql://u:p@h/t", 3)
|
dispatcher("mysql+pymysql://u:p@h/t", 3)
|
||||||
dispatcher("mysql+mysqlconnector://u:p@h/t", 4)
|
dispatcher("mysql://u:p@h/t", 4)
|
||||||
|
dispatcher("mysql+mysqlconnector://u:p@h/t", 5)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[
|
[
|
||||||
mock.call.sqlite('sqlite://', 1),
|
mock.call.sqlite('sqlite://', 1),
|
||||||
mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2),
|
mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2),
|
||||||
mock.call.mysql_mysqldb("mysql://u:p@h/t", 3),
|
mock.call.mysql_pymysql("mysql+pymysql://u:p@h/t", 3),
|
||||||
mock.call.default("mysql+mysqlconnector://u:p@h/t", 4)
|
mock.call.mysql("mysql://u:p@h/t", 4),
|
||||||
|
mock.call.mysql("mysql+mysqlconnector://u:p@h/t", 5),
|
||||||
],
|
],
|
||||||
callable_fn.mock_calls)
|
callable_fn.mock_calls)
|
||||||
|
|
||||||
@ -1081,10 +1088,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
|
|
||||||
def test_single_retval(self):
|
def test_single_retval(self):
|
||||||
dispatcher, callable_fn = self._single_fixture()
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
callable_fn.mysql_mysqldb.return_value = 5
|
callable_fn.mysql_pymysql.return_value = 5
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
dispatcher("mysql://u:p@h/t", 3), 5
|
dispatcher("mysql+pymysql://u:p@h/t", 3), 5
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_engine(self):
|
def test_engine(self):
|
||||||
@ -1097,14 +1104,25 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
callable_fn.mock_calls
|
callable_fn.mock_calls
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_url(self):
|
def test_url_pymysql(self):
|
||||||
url = sqlalchemy.engine.url.make_url(
|
url = sqlalchemy.engine.url.make_url(
|
||||||
"mysql+mysqldb://scott:tiger@localhost/test")
|
"mysql+pymysql://scott:tiger@localhost/test")
|
||||||
dispatcher, callable_fn = self._single_fixture()
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
|
|
||||||
dispatcher(url, 15)
|
dispatcher(url, 15)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[mock.call.mysql_mysqldb(url, 15)],
|
[mock.call.mysql_pymysql(url, 15)],
|
||||||
|
callable_fn.mock_calls
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_url_mysql_generic(self):
|
||||||
|
url = sqlalchemy.engine.url.make_url(
|
||||||
|
"mysql://scott:tiger@localhost/test")
|
||||||
|
dispatcher, callable_fn = self._single_fixture()
|
||||||
|
|
||||||
|
dispatcher(url, 15)
|
||||||
|
self.assertEqual(
|
||||||
|
[mock.call.mysql(url, 15)],
|
||||||
callable_fn.mock_calls
|
callable_fn.mock_calls
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1149,9 +1167,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
dispatcher, callable_fn = self._multiple_fixture()
|
dispatcher, callable_fn = self._multiple_fixture()
|
||||||
|
|
||||||
dispatcher("postgresql+pyodbc://", 1)
|
dispatcher("postgresql+pyodbc://", 1)
|
||||||
dispatcher("mysql://", 2)
|
dispatcher("mysql+pymysql://", 2)
|
||||||
dispatcher("ibm_db_sa+db2://", 3)
|
dispatcher("ibm_db_sa+db2://", 3)
|
||||||
dispatcher("postgresql+psycopg2://", 4)
|
dispatcher("postgresql+psycopg2://", 4)
|
||||||
|
dispatcher("postgresql://", 5)
|
||||||
|
|
||||||
# TODO(zzzeek): there is a deterministic order here, but we might
|
# TODO(zzzeek): there is a deterministic order here, but we might
|
||||||
# want to tweak it, or maybe provide options. default first?
|
# want to tweak it, or maybe provide options. default first?
|
||||||
@ -1161,12 +1180,18 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
|||||||
mock.call.postgresql('postgresql+pyodbc://', 1),
|
mock.call.postgresql('postgresql+pyodbc://', 1),
|
||||||
mock.call.pyodbc('postgresql+pyodbc://', 1),
|
mock.call.pyodbc('postgresql+pyodbc://', 1),
|
||||||
mock.call.default('postgresql+pyodbc://', 1),
|
mock.call.default('postgresql+pyodbc://', 1),
|
||||||
mock.call.mysql_mysqldb('mysql://', 2),
|
mock.call.mysql_pymysql('mysql+pymysql://', 2),
|
||||||
mock.call.default('mysql://', 2),
|
mock.call.mysql('mysql+pymysql://', 2),
|
||||||
|
mock.call.default('mysql+pymysql://', 2),
|
||||||
mock.call.default('ibm_db_sa+db2://', 3),
|
mock.call.default('ibm_db_sa+db2://', 3),
|
||||||
mock.call.postgresql_psycopg2('postgresql+psycopg2://', 4),
|
mock.call.postgresql_psycopg2('postgresql+psycopg2://', 4),
|
||||||
mock.call.postgresql('postgresql+psycopg2://', 4),
|
mock.call.postgresql('postgresql+psycopg2://', 4),
|
||||||
mock.call.default('postgresql+psycopg2://', 4),
|
mock.call.default('postgresql+psycopg2://', 4),
|
||||||
|
# note this is called because we resolve the default
|
||||||
|
# DBAPI for the url
|
||||||
|
mock.call.postgresql_psycopg2('postgresql://', 5),
|
||||||
|
mock.call.postgresql('postgresql://', 5),
|
||||||
|
mock.call.default('postgresql://', 5),
|
||||||
],
|
],
|
||||||
callable_fn.mock_calls
|
callable_fn.mock_calls
|
||||||
)
|
)
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
hacking>=0.10.0,<0.11
|
|
||||||
|
|
||||||
coverage>=3.6
|
|
||||||
discover
|
|
||||||
doc8 # Apache-2.0
|
|
||||||
fixtures>=0.3.14
|
|
||||||
MySQL-python
|
|
||||||
psycopg2
|
|
||||||
python-subunit>=0.0.18
|
|
||||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
|
||||||
oslosphinx>=2.5.0 # Apache-2.0
|
|
||||||
oslotest>=1.5.1 # Apache-2.0
|
|
||||||
testrepository>=0.0.18
|
|
||||||
testtools>=0.9.36,!=1.2.0
|
|
||||||
tempest-lib>=0.5.0
|
|
@ -8,12 +8,12 @@ coverage>=3.6
|
|||||||
discover
|
discover
|
||||||
doc8 # Apache-2.0
|
doc8 # Apache-2.0
|
||||||
fixtures>=0.3.14
|
fixtures>=0.3.14
|
||||||
|
PyMySQL>=0.6.2 # MIT License
|
||||||
psycopg2
|
psycopg2
|
||||||
python-subunit>=0.0.18
|
python-subunit>=0.0.18
|
||||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||||
oslosphinx>=2.5.0 # Apache-2.0
|
oslosphinx>=2.5.0 # Apache-2.0
|
||||||
oslotest>=1.5.1 # Apache-2.0
|
oslotest>=1.5.1 # Apache-2.0
|
||||||
PyMySQL>=0.6.2 # MIT License
|
|
||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
testtools>=0.9.36,!=1.2.0
|
testtools>=0.9.36,!=1.2.0
|
||||||
tempest-lib>=0.5.0
|
tempest-lib>=0.5.0
|
10
tox.ini
10
tox.ini
@ -14,7 +14,7 @@ install_command = pip install -U {opts} {packages}
|
|||||||
setenv =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/test-requirements-py2.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
commands = bash tools/pretty_tox.sh '{posargs}'
|
commands = bash tools/pretty_tox.sh '{posargs}'
|
||||||
|
|
||||||
[testenv:sqla_09]
|
[testenv:sqla_09]
|
||||||
@ -25,12 +25,12 @@ commands = pip install SQLAlchemy>=0.9.0,!=0.9.5,<1.0.0
|
|||||||
commands = pip install SQLAlchemy>=0.8.0,<0.9.0
|
commands = pip install SQLAlchemy>=0.8.0,<0.9.0
|
||||||
python setup.py testr --slowest --testr-args='{posargs}'
|
python setup.py testr --slowest --testr-args='{posargs}'
|
||||||
|
|
||||||
[testenv:py34]
|
[testenv:mysql-python]
|
||||||
setenv =
|
setenv =
|
||||||
{[testenv]setenv}
|
{[testenv]setenv}
|
||||||
OS_TEST_DBAPI_ADMIN_CONNECTION=mysql+pymysql://openstack_citest:openstack_citest@localhost/;postgresql://openstack_citest:openstack_citest@localhost/postgres;sqlite://
|
OS_TEST_DBAPI_ADMIN_CONNECTION=mysql://openstack_citest:openstack_citest@localhost/;postgresql://openstack_citest:openstack_citest@localhost/postgres;sqlite://
|
||||||
deps = -r{toxinidir}/requirements.txt
|
commands = pip install MySQL-python
|
||||||
-r{toxinidir}/test-requirements-py3.txt
|
python setup.py testr --slowest --testr-args='{posargs}'
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands = flake8
|
commands = flake8
|
||||||
|
Loading…
Reference in New Issue
Block a user