diff --git a/docs/api/rattail/db/util.rst b/docs/api/rattail/db/util.rst new file mode 100644 index 0000000000000000000000000000000000000000..4045d8f8e6275f45fc7d80b899fbf710ba0e2deb --- /dev/null +++ b/docs/api/rattail/db/util.rst @@ -0,0 +1,7 @@ +.. -*- coding: utf-8 -*- + +``rattail.db.util`` +=================== + +.. automodule:: rattail.db.util + :members: diff --git a/docs/index.rst b/docs/index.rst index c3a22945c87afa6f15b626af714d134b1bdf2d8d..9bcc60b4608470799f602ac877ee5bc9aab7e52b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,6 +16,7 @@ Package API: :maxdepth: 1 api/rattail + api/rattail/db/util api/rattail/enum api/rattail/exceptions api/rattail/filemon/util diff --git a/rattail/db/util.py b/rattail/db/util.py index a078090e3de7840041f907808d19b965d69fd048..0614f40edf9a953fac2a792467895659dfe980ee 100644 --- a/rattail/db/util.py +++ b/rattail/db/util.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2012 Lance Edgar +# Copyright © 2010-2014 Lance Edgar # # This file is part of Rattail. # @@ -26,6 +26,8 @@ Database Utilities """ +import warnings + from sqlalchemy import engine_from_config as sa_engine_from_config from ..util import load_object @@ -60,31 +62,57 @@ def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs): return sa_engine_from_config(config, prefix, **kwargs) -def get_engines(config): +def get_engines(config, section=None): """ - Fetch all database engines defined in the given config object. + Fetch all database engines defined in the given config object for a given + section. :param config: A ``ConfigParser`` instance containing app configuration. + :type section: string + :param section: Optional section name within which the configuration + options are defined. If not specified, ``'rattail.db'`` is assumed. + :returns: A dictionary of SQLAlchemy engine instances, keyed according to the config settings. + + .. note:: + Until the ``edbob`` dependency is fully removed, ``'edbob.db'`` will be + considered as a fallback section if none is specified and there is no + ``'rattail.db'`` section (or it does not contain any configuration). If + this fallback is utilized, a deprecation warning will be emitted. """ - keys = config.get('edbob.db', 'keys') - if keys: - keys = keys.split(',') - else: - keys = ['default'] - - engines = {} - cfg = config.get_dict('edbob.db') - for key in keys: - key = key.strip() - try: - engines[key] = engine_from_config(cfg, prefix='{0}.'.format(key)) - except KeyError: - if key == 'default': - try: - engines[key] = engine_from_config(cfg, prefix='sqlalchemy.') - except KeyError: - pass + + def engines_from_section(section): + keys = config.get(section, u'keys') + if keys: + keys = keys.split(u',') + else: + keys = [u'default'] + + engines = {} + cfg = config.get_dict(section) + for key in keys: + key = key.strip() + try: + engines[key] = engine_from_config(cfg, prefix=u'{0}.'.format(key)) + except KeyError: + if key == u'default': + try: + engines[key] = engine_from_config(cfg, prefix=u'sqlalchemy.') + except KeyError: + pass + return engines + + if section is not None: + return engines_from_section(section) + + engines = engines_from_section(u'rattail.db') + if engines: + return engines + + engines = engines_from_section(u'edbob.db') + if engines: + warnings.warn(u"Defining database engines in [edbob.db] is deprecated; please " + u"use the [rattail.db] section instead.", DeprecationWarning) return engines diff --git a/tests/db/test_util.py b/tests/db/test_util.py index b422a0956424eaae287a5f7da0ac4c01fb928e87..aa3ee690cfb4b2c8485396ef01b098527863412f 100644 --- a/tests/db/test_util.py +++ b/tests/db/test_util.py @@ -1,4 +1,6 @@ +# -*- coding: utf-8 -*- +import warnings from unittest import TestCase from sqlalchemy.pool import SingletonThreadPool, NullPool @@ -27,28 +29,59 @@ class TestEngineConfig(TestCase): self.assertEqual(str(engine.url), 'sqlite://') self.assertTrue(isinstance(engine.pool, NullPool)) - def test_get_engines_default(self): - config = AppConfigParser('rattail') - config.set('edbob.db', 'sqlalchemy.url', 'sqlite://') - engines = util.get_engines(config) + +class TestGetEngines(TestCase): + + def setUp(self): + self.config = AppConfigParser(u'rattail') + + def test_default_section_is_rattail_db(self): + self.config.set(u'rattail.db', u'keys', u'default') + self.config.set(u'rattail.db', u'default.url', u'sqlite://') + engines = util.get_engines(self.config) + self.assertEqual(len(engines), 1) + self.assertEqual(engines.keys()[0], u'default') + self.assertEqual(unicode(engines[u'default'].url), u'sqlite://') + + def test_custom_section_is_honored(self): + self.config.set(u'mycustomdb', u'keys', u'default') + self.config.set(u'mycustomdb', u'default.url', u'sqlite://') + engines = util.get_engines(self.config, section=u'mycustomdb') + self.assertEqual(len(engines), 1) + self.assertEqual(engines.keys()[0], u'default') + self.assertEqual(unicode(engines[u'default'].url), u'sqlite://') + + def test_default_section_falls_back_to_edbob_db(self): + self.config.set(u'edbob.db', u'keys', u'default') + self.config.set(u'edbob.db', u'default.url', u'sqlite://') + with warnings.catch_warnings(record=True) as ignore_warnings: + engines = util.get_engines(self.config) + self.assertEqual(len(engines), 1) + self.assertEqual(engines.keys()[0], u'default') + self.assertEqual(unicode(engines[u'default'].url), u'sqlite://') + + def test_default_prefix_does_not_require_keys_declaration(self): + self.config.set(u'rattail.db', u'default.url', u'sqlite://') + engines = util.get_engines(self.config) self.assertEqual(len(engines), 1) - self.assertEqual(str(engines['default'].url), 'sqlite://') - - def test_get_engines_custom(self): - config = AppConfigParser('rattail') - config.set('edbob.db', 'keys', 'host, store') - config.set('edbob.db', 'host.url', 'sqlite:///rattail.host.sqlite') - config.set('edbob.db', 'store.url', 'sqlite:///rattail.store.sqlite') - config.set('edbob.db', 'store.poolclass', 'sqlalchemy.pool:SingletonThreadPool') - engines = util.get_engines(config) + self.assertEqual(engines.keys()[0], u'default') + self.assertEqual(unicode(engines[u'default'].url), u'sqlite://') + + def test_default_prefix_falls_back_to_sqlalchemy(self): + # Still no need to define "keys" option here. + self.config.set(u'rattail.db', u'sqlalchemy.url', u'sqlite://') + engines = util.get_engines(self.config) + self.assertEqual(len(engines), 1) + self.assertEqual(engines.keys()[0], u'default') + self.assertEqual(unicode(engines[u'default'].url), u'sqlite://') + + def test_defined_keys_are_included_in_engines_result(self): + # Note there is no "default" key here. + self.config.set(u'rattail.db', u'keys', u'host, store') + self.config.set(u'rattail.db', u'host.url', u'sqlite:///rattail.host.sqlite') + self.config.set(u'rattail.db', u'store.url', u'sqlite:///rattail.store.sqlite') + engines = util.get_engines(self.config) self.assertEqual(len(engines), 2) - self.assertEqual(str(engines['host'].url), 'sqlite:///rattail.host.sqlite') - self.assertTrue(isinstance(engines['host'].pool, NullPool)) - self.assertEqual(str(engines['store'].url), 'sqlite:///rattail.store.sqlite') - self.assertTrue(isinstance(engines['store'].pool, SingletonThreadPool)) - - def test_get_engines_none(self): - config = AppConfigParser('rattail') - config.set('edbob.db', 'unknown.url', 'sqlite://') - engines = util.get_engines(config) - self.assertEqual(len(engines), 0) + self.assertEqual(sorted(engines.keys()), [u'host', u'store']) + self.assertEqual(unicode(engines[u'host'].url), u'sqlite:///rattail.host.sqlite') + self.assertEqual(unicode(engines[u'store'].url), u'sqlite:///rattail.store.sqlite')