Changeset - 0ea99a6aab68
[Not reviewed]
0 5 0
Lance Edgar - 10 years ago 2015-03-18 20:03:15
ledgar@sacfoodcoop.com
Various changes to allow custom commands to sit in front of non-Rattail database.

In particular some of the Continuum versioning code required protection
from import failures, and various assumptions about the "default" database
session were refactored.
5 files changed with 58 insertions and 15 deletions:
0 comments (0 inline, 0 general)
rattail/commands.py
Show inline comments
 
@@ -108,6 +108,27 @@ See the file COPYING.txt for more information.
 
    def __unicode__(self):
 
        return unicode(self.name)
 

	
 
    @property
 
    def db_config_section(self):
 
        """
 
        Name of section in config file which should have database connection
 
        info.  This defaults to ``'rattail.db'`` but may be overridden so the
 
        command framework can sit in front of a non-Rattail database if needed.
 

	
 
        This is used to auto-configure a "default" database engine for the app,
 
        when any command is invoked.
 
        """
 
        return 'rattail.db'
 

	
 
    @property
 
    def db_session_factory(self):
 
        """
 
        Reference to the "primary" ``Session`` class, which will be configured
 
        automatically during app startup.  Defaults to :class:`rattail.db.Session`.
 
        """
 
        from rattail.db import Session
 
        return Session
 

	
 
    def iter_subcommands(self):
 
        """
 
        Iterate over the subcommands.
 
@@ -230,10 +251,12 @@ Commands:\n""".format(self.description, self.name))
 
            except ImportError:
 
                pass            # assume no sqlalchemy
 
            else:
 
                configure_session_factory(config)
 
                configure_session_factory(config, section=self.db_config_section,
 
                                          session_factory=self.db_session_factory)
 

	
 
                # Configure Continuum versioning.
 
                if config.getboolean('rattail.db', 'versioning.enabled'):
 
                # Maybe configure Continuum versioning.
 
                if (self.db_config_section == 'rattail.db'
 
                    and config.getboolean('rattail.db', 'versioning.enabled')):
 
                    from rattail.db.continuum  import configure_versioning
 
                    configure_versioning(config)
 

	
 
@@ -544,8 +567,10 @@ class ImportSubcommand(Subcommand):
 
    """
 
    Base class for subcommands which use the data importing system.
 
    """
 
    supports_versioning = True
 

	
 
    def add_parser_args(self, parser):
 
        if self.supports_versioning:
 
            parser.add_argument('--no-versioning', action='store_true',
 
                                help="Disables versioning during the import.  This is "
 
                                "intended to be useful e.g. during initial import, where "
 
@@ -559,16 +584,18 @@ class ImportSubcommand(Subcommand):
 
                            "then all supported models will be imported.")
 

	
 
    def run(self, args):
 
        from rattail.db import Session
 
        from rattail.db.continuum  import disable_versioning
 

	
 
        log.info("begin {0} for data model(s): {1}".format(
 
                self.name, ', '.join(args.models or ["ALL"])))
 

	
 
        Session = self.parent.db_session_factory
 
        if self.supports_versioning:
 
            if args.no_versioning:
 
                from rattail.db.continuum  import disable_versioning
 
                disable_versioning()
 

	
 
            session = Session(continuum_user=self.continuum_user)
 
        else:
 
            session = Session()
 

	
 
        self.import_data(args, session)
 

	
 
        if args.dry_run:
rattail/db/changes.py
Show inline comments
 
@@ -33,7 +33,11 @@ from sqlalchemy.orm.interfaces import SessionExtension
 
from sqlalchemy.orm.session import Session
 

	
 
from rattail.core import get_uuid
 

	
 
try:
 
    from rattail.db.continuum import versioning_manager
 
except ImportError:             # assume no continuum
 
    versioning_manager = None
 

	
 

	
 
__all__ = ['record_changes']
 
@@ -85,6 +89,7 @@ class ChangeRecorder(object):
 
        # TODO: Not sure if our event replaces the one registered by Continuum,
 
        # or what.  But this appears to be necessary to keep that system
 
        # working when we enable ours...
 
        if versioning_manager:
 
            versioning_manager.before_flush(session, flush_context, instances)
 

	
 
        for instance in session.deleted:
rattail/db/importing/core.py
Show inline comments
 
@@ -75,6 +75,14 @@ class Importer(Object):
 
        self.session = session
 
        super(Importer, self).__init__(**kwargs)
 

	
 
    @property
 
    def model_module(self):
 
        """
 
        Reference to a module which contains all available / necessary data
 
        models.  By default this is ``rattail.db.model``.
 
        """
 
        return model
 

	
 
    @property
 
    def model_class(self):
 
        return getattr(model, self.__class__.__name__[:-8])
rattail/db/util.py
Show inline comments
 
@@ -148,19 +148,22 @@ def configure_session(config, session):
 
        record_changes(session, ignore_role_changes)
 

	
 

	
 
def configure_session_factory(config, session_factory=None):
 
def configure_session_factory(config, section='rattail.db', session_factory=None):
 
    """
 
    Configure a session factory using the provided settings.
 

	
 
    :param config: Object containing database configuration.
 

	
 
    :param section: Optional section name within which the configuration
 
       options are defined.  If not specified, ``'rattail.db'`` is assumed.
 

	
 
    :param session_factory: Optional session factory; if none is specified then
 
        :attr:`Session` will be assumed.
 
    """
 
    if session_factory is None:
 
        session_factory = Session
 

	
 
    engine = get_default_engine(config)
 
    engine = get_default_engine(config, section=section)
 
    if engine:
 
        session_factory.configure(bind=engine)
 

	
tests/db/test_init.py
Show inline comments
 
@@ -23,7 +23,7 @@ class TestConfigureSessionFactory(TestCase):
 
        self.Session = sessionmaker()
 

	
 
    def test_session_is_not_bound_if_no_engine_is_defined_by_config(self):
 
        configure_session_factory(self.config, self.Session)
 
        configure_session_factory(self.config, session_factory=self.Session)
 
        session = self.Session()
 
        self.assertTrue(session.bind is None)
 
        session.close()
 
@@ -33,7 +33,7 @@ class TestConfigureSessionFactory(TestCase):
 
        session = self.Session()
 
        self.assertTrue(session.bind is None)
 
        session.close()
 
        configure_session_factory(self.config, self.Session)
 
        configure_session_factory(self.config, session_factory=self.Session)
 
        session = self.Session()
 
        self.assertTrue(isinstance(session.bind, Engine))
 
        self.assertEqual(unicode(session.bind.url), u'sqlite:////a/very/custom/db')
 
@@ -55,14 +55,14 @@ class TestConfigureSessionFactory(TestCase):
 
    @patch(u'rattail.db.changes.record_changes')
 
    def test_changes_will_not_be_recorded_by_default(self, record_changes):
 
        self.config.set(u'rattail.db', u'sqlalchemy.url', u'sqlite://')
 
        configure_session_factory(self.config, self.Session)
 
        configure_session_factory(self.config, session_factory=self.Session)
 
        self.assertFalse(record_changes.called)
 

	
 
    @patch('rattail.db.util.record_changes')
 
    def test_changes_will_be_recorded_by_so_configured(self, record_changes):
 
        self.config.set(u'rattail.db', u'sqlalchemy.url', u'sqlite://')
 
        self.config.set(u'rattail.db', u'changes.record', u'true')
 
        configure_session_factory(self.config, self.Session)
 
        configure_session_factory(self.config, session_factory=self.Session)
 
        # Role changes are ignored by default.
 
        record_changes.assert_called_once_with(self.Session, True)
 

	
 
@@ -70,7 +70,7 @@ class TestConfigureSessionFactory(TestCase):
 
    def test_changes_will_still_be_recorded_with_deprecated_config(self, record_changes):
 
        self.config.set(u'rattail.db', u'sqlalchemy.url', u'sqlite://')
 
        self.config.set(u'rattail.db', u'changes.record', u'true')
 
        configure_session_factory(self.config, self.Session)
 
        configure_session_factory(self.config, session_factory=self.Session)
 
        # Role changes are ignored by default.
 
        record_changes.assert_called_once_with(self.Session, True)
 

	
 
@@ -79,6 +79,6 @@ class TestConfigureSessionFactory(TestCase):
 
        self.config.set(u'rattail.db', u'sqlalchemy.url', u'sqlite://')
 
        self.config.set(u'rattail.db', u'changes.record', u'true')
 
        self.config.set(u'rattail.db', u'changes.ignore_roles', u'false')
 
        configure_session_factory(self.config, self.Session)
 
        configure_session_factory(self.config, session_factory=self.Session)
 
        # Role changes are ignored by default; False means config works.
 
        record_changes.assert_called_once_with(self.Session, False)
0 comments (0 inline, 0 general)