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
|
||||
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
|
||||
true for OpenStack), and psycopg2 for PostgreSQL. pip will build these libs in
|
||||
your venv, so you must ensure that you have the required system packages
|
||||
installed. For Ubuntu/Debian they are python-dev, libmysqlclient-dev and
|
||||
libpq-dev. For Fedora/CentOS - gcc, python-devel, postgresql-devel and
|
||||
mysql-devel.
|
||||
oslo.db tests use PyMySQL as the default MySQL DB API driver (which is true for
|
||||
OpenStack), and psycopg2 for PostgreSQL. pip will build these libs in your
|
||||
venv, so you must ensure that you have the required system packages installed
|
||||
for psycopg2 (PyMySQL is a pure-Python implementation and so needs no
|
||||
additional system packages). For Ubuntu/Debian they are python-dev, and
|
||||
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
|
||||
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::
|
||||
|
||||
$ pip install MySQL-python
|
||||
$ pip install psycopg2
|
||||
|
||||
Or::
|
||||
|
||||
$ pip install PyMySQL
|
||||
|
||||
Or::
|
||||
|
||||
$ 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::
|
||||
|
||||
$ sudo apt-get install libmysqlclient-dev
|
||||
|
@ -455,7 +455,7 @@ class MySQLBackendImpl(BackendImpl):
|
||||
default_engine_kwargs = {'mysql_sql_mode': 'TRADITIONAL'}
|
||||
|
||||
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):
|
||||
with engine.connect() as conn:
|
||||
|
@ -46,6 +46,15 @@ class _SQLAExceptionMatcher(object):
|
||||
self.assertTrue(issubclass(exc.__class__, exception_type))
|
||||
else:
|
||||
self.assertEqual(exc.__class__.__name__, exception_type)
|
||||
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:
|
||||
self.assertEqual(exc.statement, sql)
|
||||
@ -359,10 +368,10 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
||||
self.assertInnerException(
|
||||
matched,
|
||||
"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`, "
|
||||
"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)",
|
||||
(1, 2)
|
||||
)
|
||||
@ -382,10 +391,13 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
||||
self.assertInnerException(
|
||||
matched,
|
||||
"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", '
|
||||
'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)",
|
||||
(1, 2)
|
||||
)
|
||||
@ -436,7 +448,7 @@ class TestDuplicate(TestsExceptionFilter):
|
||||
"PRIMARY KEY must be unique 'insert into t values(10)'",
|
||||
expected_columns=[])
|
||||
|
||||
def test_mysql_mysqldb(self):
|
||||
def test_mysql_pymysql(self):
|
||||
self._run_dupe_constraint_test(
|
||||
"mysql",
|
||||
'(1062, "Duplicate entry '
|
||||
@ -541,7 +553,7 @@ class TestDeadlock(TestsExceptionFilter):
|
||||
|
||||
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(
|
||||
"mysql",
|
||||
"(1213, 'Deadlock found when trying "
|
||||
@ -549,7 +561,7 @@ class TestDeadlock(TestsExceptionFilter):
|
||||
"transaction')"
|
||||
)
|
||||
|
||||
def test_mysql_mysqldb_galera_deadlock(self):
|
||||
def test_mysql_pymysql_galera_deadlock(self):
|
||||
self._run_deadlock_detect_test(
|
||||
"mysql",
|
||||
"(1205, 'Lock wait timeout exceeded; "
|
||||
|
@ -793,8 +793,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
dispatcher = orig = utils.dispatch_for_dialect("*")(
|
||||
callable_fn.default)
|
||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
||||
callable_fn.mysql_mysqldb)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||
callable_fn.mysql_pymysql)
|
||||
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||
callable_fn.mysql)
|
||||
dispatcher = dispatcher.dispatch_for("postgresql")(
|
||||
callable_fn.postgresql)
|
||||
|
||||
@ -808,7 +810,8 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
for targ in [
|
||||
callable_fn.default,
|
||||
callable_fn.sqlite,
|
||||
callable_fn.mysql_mysqldb,
|
||||
callable_fn.mysql,
|
||||
callable_fn.mysql_pymysql,
|
||||
callable_fn.postgresql,
|
||||
callable_fn.postgresql_psycopg2,
|
||||
callable_fn.pyodbc
|
||||
@ -818,8 +821,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
dispatcher = orig = utils.dispatch_for_dialect("*", multiple=True)(
|
||||
callable_fn.default)
|
||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
||||
callable_fn.mysql_mysqldb)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||
callable_fn.mysql_pymysql)
|
||||
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||
callable_fn.mysql)
|
||||
dispatcher = dispatcher.dispatch_for("postgresql+*")(
|
||||
callable_fn.postgresql)
|
||||
dispatcher = dispatcher.dispatch_for("postgresql+psycopg2")(
|
||||
@ -836,15 +841,17 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
dispatcher, callable_fn = self._single_fixture()
|
||||
dispatcher("sqlite://", 1)
|
||||
dispatcher("postgresql+psycopg2://u:p@h/t", 2)
|
||||
dispatcher("mysql://u:p@h/t", 3)
|
||||
dispatcher("mysql+mysqlconnector://u:p@h/t", 4)
|
||||
dispatcher("mysql+pymysql://u:p@h/t", 3)
|
||||
dispatcher("mysql://u:p@h/t", 4)
|
||||
dispatcher("mysql+mysqlconnector://u:p@h/t", 5)
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
mock.call.sqlite('sqlite://', 1),
|
||||
mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2),
|
||||
mock.call.mysql_mysqldb("mysql://u:p@h/t", 3),
|
||||
mock.call.default("mysql+mysqlconnector://u:p@h/t", 4)
|
||||
mock.call.mysql_pymysql("mysql+pymysql://u:p@h/t", 3),
|
||||
mock.call.mysql("mysql://u:p@h/t", 4),
|
||||
mock.call.mysql("mysql+mysqlconnector://u:p@h/t", 5),
|
||||
],
|
||||
callable_fn.mock_calls)
|
||||
|
||||
@ -962,10 +969,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
|
||||
def test_single_retval(self):
|
||||
dispatcher, callable_fn = self._single_fixture()
|
||||
callable_fn.mysql_mysqldb.return_value = 5
|
||||
callable_fn.mysql_pymysql.return_value = 5
|
||||
|
||||
self.assertEqual(
|
||||
dispatcher("mysql://u:p@h/t", 3), 5
|
||||
dispatcher("mysql+pymysql://u:p@h/t", 3), 5
|
||||
)
|
||||
|
||||
def test_engine(self):
|
||||
@ -978,14 +985,25 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
callable_fn.mock_calls
|
||||
)
|
||||
|
||||
def test_url(self):
|
||||
def test_url_pymysql(self):
|
||||
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(url, 15)
|
||||
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
|
||||
)
|
||||
|
||||
@ -1030,9 +1048,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
dispatcher, callable_fn = self._multiple_fixture()
|
||||
|
||||
dispatcher("postgresql+pyodbc://", 1)
|
||||
dispatcher("mysql://", 2)
|
||||
dispatcher("mysql+pymysql://", 2)
|
||||
dispatcher("ibm_db_sa+db2://", 3)
|
||||
dispatcher("postgresql+psycopg2://", 4)
|
||||
dispatcher("postgresql://", 5)
|
||||
|
||||
# TODO(zzzeek): there is a deterministic order here, but we might
|
||||
# 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.pyodbc('postgresql+pyodbc://', 1),
|
||||
mock.call.default('postgresql+pyodbc://', 1),
|
||||
mock.call.mysql_mysqldb('mysql://', 2),
|
||||
mock.call.default('mysql://', 2),
|
||||
mock.call.mysql_pymysql('mysql+pymysql://', 2),
|
||||
mock.call.mysql('mysql+pymysql://', 2),
|
||||
mock.call.default('mysql+pymysql://', 2),
|
||||
mock.call.default('ibm_db_sa+db2://', 3),
|
||||
mock.call.postgresql_psycopg2('postgresql+psycopg2://', 4),
|
||||
mock.call.postgresql('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
|
||||
)
|
||||
|
@ -46,6 +46,15 @@ class _SQLAExceptionMatcher(object):
|
||||
self.assertTrue(issubclass(exc.__class__, exception_type))
|
||||
else:
|
||||
self.assertEqual(exc.__class__.__name__, exception_type)
|
||||
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:
|
||||
self.assertEqual(exc.statement, sql)
|
||||
@ -362,10 +371,10 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
||||
self.assertInnerException(
|
||||
matched,
|
||||
"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`, "
|
||||
"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)",
|
||||
(1, 2)
|
||||
)
|
||||
@ -390,10 +399,13 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
||||
self.assertInnerException(
|
||||
matched,
|
||||
"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", '
|
||||
'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)",
|
||||
(1, 2)
|
||||
)
|
||||
@ -414,11 +426,14 @@ class TestReferenceErrorMySQL(TestReferenceErrorSQLite,
|
||||
self.assertInnerException(
|
||||
matched,
|
||||
"IntegrityError",
|
||||
"(1451, 'cannot delete or update a parent row: a foreign key "
|
||||
(
|
||||
1451,
|
||||
"Cannot delete or update a parent row: a foreign key "
|
||||
"constraint fails (`{0}`.`resource_entity`, "
|
||||
"constraint `foo_fkey` "
|
||||
"foreign key (`foo_id`) references "
|
||||
"`resource_foo` (`id`))')".format(self.engine.url.database),
|
||||
"`resource_foo` (`id`))".format(self.engine.url.database)
|
||||
),
|
||||
"DELETE FROM resource_foo",
|
||||
(),
|
||||
)
|
||||
@ -483,7 +498,7 @@ class TestDuplicate(TestsExceptionFilter):
|
||||
"PRIMARY KEY must be unique 'insert into t values(10)'",
|
||||
expected_columns=[])
|
||||
|
||||
def test_mysql_mysqldb(self):
|
||||
def test_mysql_pymysql(self):
|
||||
self._run_dupe_constraint_test(
|
||||
"mysql",
|
||||
'(1062, "Duplicate entry '
|
||||
@ -606,7 +621,7 @@ class TestDeadlock(TestsExceptionFilter):
|
||||
|
||||
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(
|
||||
"mysql",
|
||||
"(1213, 'Deadlock found when trying "
|
||||
@ -614,7 +629,7 @@ class TestDeadlock(TestsExceptionFilter):
|
||||
"transaction')"
|
||||
)
|
||||
|
||||
def test_mysql_mysqldb_galera_deadlock(self):
|
||||
def test_mysql_pymysql_galera_deadlock(self):
|
||||
self._run_deadlock_detect_test(
|
||||
"mysql",
|
||||
"(1205, 'Lock wait timeout exceeded; "
|
||||
|
@ -558,7 +558,7 @@ class CreateEngineTest(oslo_test.BaseTestCase):
|
||||
|
||||
def test_queuepool_args(self):
|
||||
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)
|
||||
self.assertEqual(self.args['pool_size'], 10)
|
||||
self.assertEqual(self.args['max_overflow'], 10)
|
||||
@ -609,6 +609,12 @@ class CreateEngineTest(oslo_test.BaseTestCase):
|
||||
self.assertEqual(self.args['connect_args'],
|
||||
{'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):
|
||||
engines._init_connection_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("*")(
|
||||
callable_fn.default)
|
||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
||||
callable_fn.mysql_mysqldb)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||
callable_fn.mysql_pymysql)
|
||||
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||
callable_fn.mysql)
|
||||
dispatcher = dispatcher.dispatch_for("postgresql")(
|
||||
callable_fn.postgresql)
|
||||
|
||||
@ -927,7 +929,8 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
for targ in [
|
||||
callable_fn.default,
|
||||
callable_fn.sqlite,
|
||||
callable_fn.mysql_mysqldb,
|
||||
callable_fn.mysql,
|
||||
callable_fn.mysql_pymysql,
|
||||
callable_fn.postgresql,
|
||||
callable_fn.postgresql_psycopg2,
|
||||
callable_fn.pyodbc
|
||||
@ -937,8 +940,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
dispatcher = orig = utils.dispatch_for_dialect("*", multiple=True)(
|
||||
callable_fn.default)
|
||||
dispatcher = dispatcher.dispatch_for("sqlite")(callable_fn.sqlite)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+mysqldb")(
|
||||
callable_fn.mysql_mysqldb)
|
||||
dispatcher = dispatcher.dispatch_for("mysql+pymysql")(
|
||||
callable_fn.mysql_pymysql)
|
||||
dispatcher = dispatcher.dispatch_for("mysql")(
|
||||
callable_fn.mysql)
|
||||
dispatcher = dispatcher.dispatch_for("postgresql+*")(
|
||||
callable_fn.postgresql)
|
||||
dispatcher = dispatcher.dispatch_for("postgresql+psycopg2")(
|
||||
@ -955,15 +960,17 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
dispatcher, callable_fn = self._single_fixture()
|
||||
dispatcher("sqlite://", 1)
|
||||
dispatcher("postgresql+psycopg2://u:p@h/t", 2)
|
||||
dispatcher("mysql://u:p@h/t", 3)
|
||||
dispatcher("mysql+mysqlconnector://u:p@h/t", 4)
|
||||
dispatcher("mysql+pymysql://u:p@h/t", 3)
|
||||
dispatcher("mysql://u:p@h/t", 4)
|
||||
dispatcher("mysql+mysqlconnector://u:p@h/t", 5)
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
mock.call.sqlite('sqlite://', 1),
|
||||
mock.call.postgresql("postgresql+psycopg2://u:p@h/t", 2),
|
||||
mock.call.mysql_mysqldb("mysql://u:p@h/t", 3),
|
||||
mock.call.default("mysql+mysqlconnector://u:p@h/t", 4)
|
||||
mock.call.mysql_pymysql("mysql+pymysql://u:p@h/t", 3),
|
||||
mock.call.mysql("mysql://u:p@h/t", 4),
|
||||
mock.call.mysql("mysql+mysqlconnector://u:p@h/t", 5),
|
||||
],
|
||||
callable_fn.mock_calls)
|
||||
|
||||
@ -1081,10 +1088,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
|
||||
def test_single_retval(self):
|
||||
dispatcher, callable_fn = self._single_fixture()
|
||||
callable_fn.mysql_mysqldb.return_value = 5
|
||||
callable_fn.mysql_pymysql.return_value = 5
|
||||
|
||||
self.assertEqual(
|
||||
dispatcher("mysql://u:p@h/t", 3), 5
|
||||
dispatcher("mysql+pymysql://u:p@h/t", 3), 5
|
||||
)
|
||||
|
||||
def test_engine(self):
|
||||
@ -1097,14 +1104,25 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
callable_fn.mock_calls
|
||||
)
|
||||
|
||||
def test_url(self):
|
||||
def test_url_pymysql(self):
|
||||
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(url, 15)
|
||||
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
|
||||
)
|
||||
|
||||
@ -1149,9 +1167,10 @@ class TestDialectFunctionDispatcher(test_base.BaseTestCase):
|
||||
dispatcher, callable_fn = self._multiple_fixture()
|
||||
|
||||
dispatcher("postgresql+pyodbc://", 1)
|
||||
dispatcher("mysql://", 2)
|
||||
dispatcher("mysql+pymysql://", 2)
|
||||
dispatcher("ibm_db_sa+db2://", 3)
|
||||
dispatcher("postgresql+psycopg2://", 4)
|
||||
dispatcher("postgresql://", 5)
|
||||
|
||||
# TODO(zzzeek): there is a deterministic order here, but we might
|
||||
# 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.pyodbc('postgresql+pyodbc://', 1),
|
||||
mock.call.default('postgresql+pyodbc://', 1),
|
||||
mock.call.mysql_mysqldb('mysql://', 2),
|
||||
mock.call.default('mysql://', 2),
|
||||
mock.call.mysql_pymysql('mysql+pymysql://', 2),
|
||||
mock.call.mysql('mysql+pymysql://', 2),
|
||||
mock.call.default('mysql+pymysql://', 2),
|
||||
mock.call.default('ibm_db_sa+db2://', 3),
|
||||
mock.call.postgresql_psycopg2('postgresql+psycopg2://', 4),
|
||||
mock.call.postgresql('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
|
||||
)
|
||||
|
@ -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
|
||||
doc8 # Apache-2.0
|
||||
fixtures>=0.3.14
|
||||
PyMySQL>=0.6.2 # MIT License
|
||||
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
|
||||
PyMySQL>=0.6.2 # MIT License
|
||||
testrepository>=0.0.18
|
||||
testtools>=0.9.36,!=1.2.0
|
||||
tempest-lib>=0.5.0
|
10
tox.ini
10
tox.ini
@ -14,7 +14,7 @@ install_command = pip install -U {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements-py2.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = bash tools/pretty_tox.sh '{posargs}'
|
||||
|
||||
[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
|
||||
python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:py34]
|
||||
[testenv:mysql-python]
|
||||
setenv =
|
||||
{[testenv]setenv}
|
||||
OS_TEST_DBAPI_ADMIN_CONNECTION=mysql+pymysql://openstack_citest:openstack_citest@localhost/;postgresql://openstack_citest:openstack_citest@localhost/postgres;sqlite://
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements-py3.txt
|
||||
OS_TEST_DBAPI_ADMIN_CONNECTION=mysql://openstack_citest:openstack_citest@localhost/;postgresql://openstack_citest:openstack_citest@localhost/postgres;sqlite://
|
||||
commands = pip install MySQL-python
|
||||
python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
|
Loading…
Reference in New Issue
Block a user