Changeset - 446a63b6ebbf
[Not reviewed]
0 55 1
Lance Edgar (lance) - 3 months ago 2024-07-11 13:06:54
lance@edbob.org
feat: move some app model logic to wuttjamaican

previously the "canonical" way to get the model was via
config.get_model() - but now the canonical way is simply app.model

note that for now, even though wuttjamaican provides a default model,
the rattail model does not (yet?) inherit from it. also since
wuttjamaican does not yet support versioning, that feature remains in
rattail only for now.

various legacy things have been deprecated and will warn if used by
other apps.
56 files changed with 297 insertions and 266 deletions:
0 comments (0 inline, 0 general)
docs/api/index.rst
Show inline comments
 
@@ -31,6 +31,7 @@ attributes and method signatures etc.
 
   rattail/commands.make_appdir
 
   rattail/commands.typer
 
   rattail/config
 
   rattail/core
 
   rattail/csvutil
 
   rattail/custorders
 
   rattail/datasync/index
docs/api/rattail/core.rst
Show inline comments
 
new file 100644
 

	
 
``rattail.core``
 
================
 

	
 
.. automodule:: rattail.core
 
   :members:
docs/narr/config.rst
Show inline comments
 
@@ -206,7 +206,9 @@ This means your script probably should look something like this::
 

	
 
   def do_stuff(config):
 
       from rattail.db import Session
 
       model = config.get_model()   # or e.g. 'from poser.db import model'
 

	
 
       app = config.get_app()
 
       model = app.model        # or e.g. 'from poser.db import model'
 

	
 
       session = Session()
 
       print(session.query(model.Product).count())
pyproject.toml
Show inline comments
 
@@ -49,7 +49,7 @@ dependencies = [
 
        "texttable",
 
        "typer",
 
        "typing-extensions",
 
        "WuttJamaican>=0.4.0",
 
        "WuttJamaican>=0.6.0",
 
        "xlrd",
 
]
 

	
rattail/app.py
Show inline comments
 
@@ -49,7 +49,7 @@ from rattail.util import (load_object, load_entry_points,
 
                          NOTSET)
 
from rattail.files import temp_path, resource_path
 
from rattail.mail import send_email
 
from rattail.core import get_uuid, Object
 
from rattail.core import Object
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -73,6 +73,8 @@ class AppHandler(WuttaAppHandler):
 
    :meth:`~wuttjamaican:wuttjamaican.conf.WuttaConfig.get_app()` on
 
    the config object if you need the app handler.
 
    """
 
    default_app_title = "Rattail"
 
    default_model_spec = 'rattail.db.model'
 
    default_autocompleters = {
 
        'brands': 'rattail.autocomplete.brands:BrandAutocompleter',
 
        'customers': 'rattail.autocomplete.customers:CustomerAutocompleter',
 
@@ -104,26 +106,24 @@ class AppHandler(WuttaAppHandler):
 
        """
 
        return self.config.get_enum()
 

	
 
    @property
 
    def model(self):
 
        """
 
        Property which returns a reference to the DB model module.
 

	
 
        Ultimately this is the same as calling
 
        :meth:`rattail.config.RattailConfig.get_model()`.
 
        """
 
        return self.config.get_model()
 

	
 
    def get_title(self, default='Rattail'):
 
        """
 
        Returns the configured title (name) of the app.
 

	
 
        :param default: Value to be returned if there is no app title
 
           configured.
 

	
 
        :returns: Title for the app.
 
        """
 
        return self.config.get('rattail', 'app_title', default=default)
 
    # TODO: once all config is updated to use model_spec, this method
 
    # can be removed so upstream logic is used instead
 
    def get_model(self):
 
        """ """
 
        if 'model' not in self.__dict__:
 
            spec = self.config.get(f'{self.appname}.model_spec',
 
                                   usedb=False)
 
            if not spec:
 
                spec = self.config.get(f'{self.appname}.model',
 
                                       usedb=False)
 
                if spec:
 
                    warnings.warn(f"config for '{self.appname}.model' is deprecated; "
 
                                  f"you must set '{self.appname}.model_spec' instead",
 
                                  DeprecationWarning)
 
                else:
 
                    spec = self.default_model_spec
 
            self.model = importlib.import_module(spec)
 
        return self.model
 

	
 
    def get_node_title(self, default='Rattail'):
 
        """
 
@@ -498,11 +498,7 @@ class AppHandler(WuttaAppHandler):
 
        """
 
        import sqlalchemy as sa
 

	
 
        # nb. this can happen "too early" (?) in which case self.model
 
        # may not be set correctly (?) ugh hoping wutta fixes some day
 
        #model = self.get_model()
 
        model = self.model
 

	
 
        return session.query(model.Store)\
 
                      .filter(sa.or_(
 
                          model.Store.archived == False,
 
@@ -1731,14 +1727,6 @@ class AppHandler(WuttaAppHandler):
 
                kwargs['dir'] = tmpdir
 
        return temp_path(**kwargs)
 

	
 
    def make_uuid(self):
 
        """
 
        Generate a new UUID value.
 

	
 
        :returns: UUID value as 32-character string.
 
        """
 
        return get_uuid()
 

	
 
    def maxlen(self, attr):
 
        """
 
        Return the max size (length) for the given model attribute.
rattail/autocomplete/base.py
Show inline comments
 
@@ -75,7 +75,7 @@ class Autocompleter:
 
        self.app = self.config.get_app()
 
        self.enum = config.get_enum()
 
        try:
 
            self.model = config.get_model()
 
            self.model = self.app.model
 
        except ImportError:
 
            pass
 

	
rattail/batch/handlers.py
Show inline comments
 
@@ -68,8 +68,8 @@ class BatchHandler(object):
 
    def __init__(self, config, **kwargs):
 
        self.config = config
 
        self.app = self.config.get_app()
 
        self.model = self.app.model
 
        self.enum = config.get_enum()
 
        self.model = config.get_model()
 

	
 
    @property
 
    def batch_model_class(self):
 
@@ -984,7 +984,8 @@ def get_batch_types(config):
 
    """
 
    Returns the list of available batch type keys.
 
    """
 
    model = config.get_model()
 
    app = config.get_app()
 
    model = app.model
 

	
 
    keys = []
 
    for name in dir(model):
rattail/bouncer/handler.py
Show inline comments
 
@@ -69,7 +69,7 @@ class BounceHandler:
 
        self.enum = config.get_enum()
 

	
 
        try:
 
            self.model = config.get_model()
 
            self.model = self.app.model
 
        except ImportError:
 
            pass
 

	
rattail/commands/typer.py
Show inline comments
 
@@ -224,8 +224,9 @@ def make_cli_config(ctx):
 
    # TODO: what about web apps etc.? i guess i was only having the problem
 
    # for some importers, e.g. basic CORE API -> Rattail w/ the schema
 
    # extensions in place from rattail-corepos
 
    app = config.get_app()
 
    try:
 
        config.get_model()
 
        app.model
 
    except ImportError:
 
        pass
 

	
rattail/config.py
Show inline comments
 
@@ -284,12 +284,15 @@ class RattailConfig(WuttaConfig):
 

	
 
    def get_model(self):
 
        """
 
        Returns a reference to the module containing data models;
 
        defaults to :mod:`rattail.db.model`.
 
        DEPRECATED; use
 
        :attr:`~wuttjamaican:wuttjamaican.app.AppHandler.model`
 
        instead.
 
        """
 
        spec = self.get('rattail', 'model', usedb=False,
 
                        default='rattail.db.model')
 
        return importlib.import_module(spec)
 
        warnings.warn("RattailConfig.get_model() is deprecated; "
 
                      "please use AppHandler.model instead",
 
                      DeprecationWarning, stacklevel=2)
 
        app = self.get_app()
 
        return app.model
 

	
 
    def get_enum(self, **kwargs):
 
        """
 
@@ -699,7 +702,7 @@ class ConfigProfile(object):
 
    def __init__(self, config, key, **kwargs):
 
        self.config = config
 
        self.app = self.config.get_app()
 
        self.model = self.config.get_model()
 
        self.model = self.app.model
 
        self.enum = self.config.get_enum()
 
        self.key = key
 
        self.prefix = kwargs.pop('prefix', key)
rattail/core.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2017 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -24,9 +24,9 @@
 
Core Stuff
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 
import warnings
 

	
 
from uuid import uuid1
 
from wuttjamaican.util import make_uuid
 

	
 

	
 
# Arbitrary object used to explicitly denote an unspecified value, e.g. for
 
@@ -62,9 +62,10 @@ class Object(object):
 

	
 
def get_uuid():
 
    """
 
    Generate a universally-unique identifier.
 

	
 
    :returns: A 32-character hex string.
 
    DEPRECATED; use
 
    :func:`~wuttjamaican:wuttjamaican.util.make_uuid()` instead.
 
    """
 

	
 
    return uuid1().hex
 
    warnings.warn("rattail.core.get_uuid() is deprecated; "
 
                  "please use wuttjamaican.util.make_uuid() instead",
 
                  DeprecationWarning, stacklevel=2)
 
    return make_uuid()
rattail/datasync/config.py
Show inline comments
 
@@ -210,7 +210,7 @@ def get_consumer_keys(config, profile_key, include_disabled=False):
 
        # maybe also look for config settings in DB
 
        if config.usedb:
 
            app = config.get_app()
 
            model = config.get_model()
 
            model = app.model
 
            session = app.make_session()
 
            settings = session.query(model.Setting)\
 
                              .filter(model.Setting.name.like(f'rattail.datasync.{profile_key}.consumer.%.spec'))\
 
@@ -248,7 +248,7 @@ def get_profile_keys(config, include_disabled=False):
 
        # maybe also look for config settings in DB
 
        if config.usedb:
 
            app = config.get_app()
 
            model = config.get_model()
 
            model = app.model
 
            session = app.make_session()
 
            settings = session.query(model.Setting)\
 
                              .filter(model.Setting.name.like('rattail.datasync.%.watcher.spec'))\
rattail/datasync/consumers.py
Show inline comments
 
@@ -41,9 +41,9 @@ class DataSyncConsumer(object):
 
        self.dbkey = dbkey
 
        self.runas_username = runas
 
        self.watcher = watcher
 
        self.model = config.get_model()
 
        self.enum = config.get_enum()
 
        self.app = config.get_app()
 
        self.model = self.app.model
 
        self.enum = config.get_enum()
 

	
 
    def setup(self):
 
        """
rattail/datasync/daemon.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2022 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -24,8 +24,6 @@
 
DataSync for Linux
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import sys
 
import time
 
import logging
 
@@ -263,7 +261,7 @@ def record_or_process_changes(config, watcher, changes):
 
       ``True`` if no error.
 
    """
 
    app = config.get_app()
 
    model = config.get_model()
 
    model = app.model
 

	
 
    # if watcher consumes itself, then it will process its own
 
    # changes.  note that there are no assumptions made about the
 
@@ -424,7 +422,7 @@ def consume_current_changes(config, consumer):
 
    :returns: ``True`` if all goes well, ``False`` if error.
 
    """
 
    app = config.get_app()
 
    model = config.get_model()
 
    model = app.model
 

	
 
    try:
 
        session = app.make_session()
 
@@ -542,7 +540,7 @@ def consume_changes_from(config, session, consumer, obtained):
 
    :returns: ``True`` if all goes well, ``False`` if error.
 
    """
 
    app = config.get_app()
 
    model = config.get_model()
 
    model = app.model
 

	
 
    # we only want changes "obtained" at the given time.  however, at least
 
    # until all code has been refactored, we must take two possibilities into
rattail/datasync/rattail.py
Show inline comments
 
@@ -42,7 +42,7 @@ class RattailWatcher(DataSyncWatcher):
 
    def __init__(self, *args, **kwargs):
 
        super().__init__(*args, **kwargs)
 
        self.engine = self.config.rattail_engines[self.dbkey]
 
        self.topo_sortkey = make_topo_sortkey(self.config.get_model())
 
        self.topo_sortkey = make_topo_sortkey(self.app.model)
 

	
 
    def get_changes(self, lastrun):
 
        """
 
@@ -186,7 +186,7 @@ class RattailConsumer(DataSyncConsumer):
 
        Subclasses may override this if they have extended the schema.
 
        Defaults to ``rattail.db.model``.
 
        """
 
        return self.config.get_model()
 
        return self.app.model
 

	
 
    def process_changes(self, host_session, changes):
 
        """
 
@@ -430,7 +430,7 @@ class FromRattailToRattailExportConsumer(FromRattailToRattailBase):
 
    def __init__(self, *args, **kwargs):
 
        super().__init__(*args, **kwargs)
 
        self.target_engine = self.config.rattail_engines[self.dbkey]
 
        self.model = self.config.get_model()
 
        self.model = self.app.model
 

	
 
    def make_target_session(self):
 
        return self.app.make_session(bind=self.target_engine)
 
@@ -473,7 +473,7 @@ class FromRattailToRattailImportConsumer(FromRattailToRattailBase):
 
        super().__init__(*args, **kwargs)
 
        self.host_engine = self.config.rattail_engines[self.watcher.dbkey]
 
        self.local_engine = self.config.rattail_engines[self.dbkey]
 
        self.model = self.config.get_model()
 
        self.model = self.app.model
 

	
 
    def process_changes(self, session, changes):
 
        self.host_session = self.app.make_session(bind=self.host_engine)
rattail/datasync/watchers.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2023 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -28,10 +28,8 @@ import datetime
 

	
 
import sqlalchemy as sa
 

	
 
from rattail.time import make_utc, localtime
 

	
 

	
 
class DataSyncWatcher(object):
 
class DataSyncWatcher:
 
    """
 
    Base class for all DataSync watchers.
 
    """
 
@@ -56,8 +54,8 @@ class DataSyncWatcher(object):
 
        self.key = key
 
        self.dbkey = dbkey
 
        self.delay = 1 # seconds
 
        self.model = self.config.get_model()
 
        self.app = self.config.get_app()
 
        self.model = self.app.model
 

	
 
    def setup(self):
 
        """
 
@@ -71,11 +69,11 @@ class DataSyncWatcher(object):
 
        "other" server.
 
        """
 
        # get current time according to "other" server, and convert to UTC
 
        before = make_utc()
 
        before = self.app.make_utc()
 
        other_now = session.execute(sa.text('select current_timestamp'))\
 
                           .fetchone()[0]
 
        after = make_utc()
 
        other_now = make_utc(localtime(self.config, other_now))
 
        after = self.app.make_utc()
 
        other_now = self.app.make_utc(self.app.localtime(other_now))
 

	
 
        # get current time according to local server (i.e. Rattail), in UTC
 
        local_now = after
 
@@ -92,7 +90,7 @@ class DataSyncWatcher(object):
 
        drift += datetime.timedelta(seconds=1)
 

	
 
        # convert lastrun time to local timezone, for "other" queries
 
        lastrun = localtime(self.config, lastrun, tzinfo=False)
 
        lastrun = self.app.localtime(lastrun, tzinfo=False)
 

	
 
        # and finally, apply the drift
 
        return lastrun - drift
rattail/db/__init__.py
Show inline comments
 
@@ -84,7 +84,8 @@ if sqlalchemy:
 
              ``uuid`` or ``username`` for one.
 
            """
 
            if self.rattail_config:
 
                model = self.rattail_config.get_model()
 
                app = self.rattail_config.get_app()
 
                model = app.model
 
            else:
 
                from rattail.db import model
 

	
rattail/db/alembic/env.py
Show inline comments
 
@@ -21,7 +21,8 @@ rattail_config = make_config(alembic_config.config_file_name,
 

	
 
# Configure Continuum...this is trickier than I'd like.
 
configure_versioning(rattail_config, force=True)
 
model = rattail_config.get_model()
 
app = rattail_config.get_app()
 
model = app.model
 
orm.configure_mappers()
 

	
 
# add your model's MetaData object here
rattail/db/alembic/versions/3e79dd89fe12_add_label_handheld_batch_es_tie.py
Show inline comments
 
@@ -19,7 +19,7 @@ from alembic import op
 
import sqlalchemy as sa
 
import rattail.db.types
 
from sqlalchemy.dialects import postgresql
 
from rattail.core import get_uuid
 
from wuttjamaican.util import make_uuid
 

	
 

	
 
def upgrade():
 
@@ -43,7 +43,7 @@ def upgrade():
 
    for row in cursor.fetchall():
 
        if row['handheld_batch_uuid']:
 
            op.get_bind().execute(label_batch_handheld.insert().values({
 
                'uuid': get_uuid(),
 
                'uuid': make_uuid(),
 
                'batch_uuid': row['uuid'],
 
                'handheld_uuid': row['handheld_batch_uuid'],
 
                'ordinal': 1,
rattail/db/alembic/versions/a5a7358ed27f_inventory_from_handheld_batches.py
Show inline comments
 
@@ -19,7 +19,7 @@ from alembic import op
 
import sqlalchemy as sa
 
import rattail.db.types
 
from sqlalchemy.dialects import postgresql
 
from rattail.core import get_uuid
 
from wuttjamaican.util import make_uuid
 

	
 

	
 
def upgrade():
 
@@ -43,7 +43,7 @@ def upgrade():
 
    for row in cursor.fetchall():
 
        if row['handheld_batch_uuid']:
 
            op.get_bind().execute(batch_inventory_handheld.insert().values({
 
                'uuid': get_uuid(),
 
                'uuid': make_uuid(),
 
                'batch_uuid': row['uuid'],
 
                'handheld_uuid': row['handheld_batch_uuid'],
 
                'ordinal': 1,
rattail/db/changes.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2023 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -36,9 +36,7 @@ try:
 
except ImportError:
 
    listen = None # old SQLAlchemy; will have to work around that, below
 

	
 
from rattail.db import model
 
from rattail.core import get_uuid
 
from rattail.util import load_object
 
from rattail.db.model import Setting, Change, DataSyncChange
 

	
 
try:
 
    from rattail.db.continuum import versioning_manager
 
@@ -62,9 +60,10 @@ def record_changes(session, recorder=None, config=None):
 
        recorder = recorder(config)
 
    elif recorder is None:
 
        if config:
 
            app = config.get_app()
 
            spec = config.get('rattail.db', 'changes.recorder', usedb=False)
 
            if spec:
 
                recorder = load_object(spec)(config)
 
                recorder = app.load_object(spec)(config)
 
        if not recorder:
 
            recorder = ChangeRecorder(config)
 
    else:
 
@@ -83,7 +82,7 @@ def record_changes(session, recorder=None, config=None):
 
    session.rattail_change_recorder = recorder
 

	
 

	
 
class ChangeRecorder(object):
 
class ChangeRecorder:
 
    """
 
    Listener for session ``before_flush`` events.
 

	
 
@@ -92,9 +91,9 @@ class ChangeRecorder(object):
 
    data propagation.
 
    """
 
    ignored_classes = (
 
        model.Setting,
 
        model.Change,
 
        model.DataSyncChange,
 
        Setting,
 
        Change,
 
        DataSyncChange,
 
    )
 

	
 
    # once upon a time we supposedly needed to specify `passive=True` when
 
@@ -108,7 +107,8 @@ class ChangeRecorder(object):
 
    def __init__(self, config):
 
        self.config = config
 
        if self.config:
 
            self.model = config.get_model()
 
            self.app = self.config.get_app()
 
            self.model = self.app.model
 

	
 
    def __call__(self, session, flush_context, instances):
 
        """
 
@@ -141,6 +141,9 @@ class ChangeRecorder(object):
 
        """
 
        Return ``True`` if changes for the given object should be ignored.
 
        """
 
        app = self.config.get_app()
 
        model = self.app.model
 

	
 
        # ignore certain classes per declaration
 
        if isinstance(obj, self.ignored_classes):
 
            return True
 
@@ -172,6 +175,8 @@ class ChangeRecorder(object):
 
        """
 
        Record changes as appropriate, for the given 'deleted' object.
 
        """
 
        model = self.app.model
 

	
 
        # TODO: should perhaps find a "cleaner" way to handle these..?
 

	
 
        # mark Person as dirty, when contact info is removed
 
@@ -228,6 +233,8 @@ class ChangeRecorder(object):
 
        :returns: ``True`` if a change was recorded, or ``False`` if it was
 
           ignored.
 
        """
 
        model = self.app.model
 

	
 
        # TODO: this check is now redundant due to `ignore_object()`
 
        # No need to record changes for changes.
 
        if isinstance(instance, (model.Change, model.DataSyncChange)):
 
@@ -261,6 +268,7 @@ class ChangeRecorder(object):
 
        """
 
        Record a change, by creating a new change record in the session.
 
        """
 
        model = self.app.model
 
        session.add(model.Change(**kwargs))
 
        if not quiet:
 
            log.debug("recorded {} for {} with key: {}".format(
 
@@ -288,7 +296,7 @@ class ChangeRecorder(object):
 

	
 
        mapper = object_mapper(instance)
 
        if not mapper.columns['uuid'].foreign_keys:
 
            instance.uuid = get_uuid()
 
            instance.uuid = self.app.make_uuid()
 
            return
 

	
 
        for prop in mapper.iterate_properties:
 
@@ -302,7 +310,7 @@ class ChangeRecorder(object):
 
                    instance.uuid = foreign_instance.uuid
 
                    return
 

	
 
        instance.uuid = get_uuid()
 
        instance.uuid = self.app.make_uuid()
 
        log.error("unexpected scenario; generated new UUID for instance: {0}".format(repr(instance)))
 

	
 

	
rattail/db/config.py
Show inline comments
 
@@ -95,8 +95,9 @@ def configure_versioning(config, force=False, manager=None, plugins=None, **kwar
 
        continuum.make_versioned(**kwargs)
 

	
 
        # TODO: is this the best way/place to confirm versioning?
 
        app = config.get_app()
 
        try:
 
            model = config.get_model()
 
            model = app.model
 
            configure_mappers()
 
            transaction_class = continuum.transaction_class(model.User)
 
            config.versioning_has_been_enabled = True
rattail/db/core.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2018 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -24,11 +24,9 @@
 
Core Data Stuff
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import sqlalchemy as sa
 

	
 
from rattail.core import get_uuid
 
from wuttjamaican.util import make_uuid
 

	
 

	
 
def uuid_column(*args, **kwargs):
 
@@ -38,7 +36,7 @@ def uuid_column(*args, **kwargs):
 

	
 
    kwargs.setdefault('primary_key', True)
 
    kwargs.setdefault('nullable', False)
 
    kwargs.setdefault('default', get_uuid)
 
    kwargs.setdefault('default', make_uuid)
 
    return sa.Column(sa.String(length=32), *args, **kwargs)
 

	
 

	
rattail/emails.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2023 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -30,9 +30,7 @@ import socket
 
from traceback import format_exception
 

	
 
from rattail.mail import Email
 
from rattail.util import load_object, simple_error
 
from rattail.core import Object
 
from rattail.time import make_utc, localtime
 
from rattail.util import simple_error
 

	
 

	
 
class ProblemReportEmail(Email):
 
@@ -152,7 +150,8 @@ class new_email_requested(Email):
 
    default_subject = "Email Update Request"
 

	
 
    def sample_data(self, request):
 
        model = request.rattail_config.get_model()
 
        app = request.rattail_config.get_app()
 
        model = app.model
 
        customer = model.Customer(name="Fred Flintstone")
 
        return {
 
            'user': request.user,
 
@@ -171,7 +170,8 @@ class new_phone_requested(Email):
 
    default_subject = "Phone Update Request"
 

	
 
    def sample_data(self, request):
 
        model = request.rattail_config.get_model()
 
        app = request.rattail_config.get_app()
 
        model = app.model
 
        customer = model.Customer(name="Fred Flintstone")
 
        return {
 
            'user': request.user,
 
@@ -216,7 +216,8 @@ class ImporterEmail(Email):
 
    handler_spec = None
 

	
 
    def get_handler(self, config):
 
        return load_object(self.handler_spec)(config)
 
        app = config.get_app()
 
        return app.load_object(self.handler_spec)(config)
 

	
 
    def get_default_subject(self, **data):
 
        host_title = data.get('host_title')
 
@@ -230,8 +231,9 @@ class ImporterEmail(Email):
 
        return "Changes for {} -> {}".format(host_title, local_title)
 

	
 
    def sample_data(self, request):
 
        app = request.rattail_config.get_app()
 
        handler = self.get_handler(request.rattail_config)
 
        obj = Object()
 
        obj = app.make_object()
 
        local_data = {
 
            'foo': 42,
 
            'bar': True,
 
@@ -358,7 +360,8 @@ class rattail_problems_stale_inventory_batch(ProblemReportEmail):
 
    abstract = False
 

	
 
    def sample_data(self, request):
 
        model = self.config.get_model()
 
        app = request.rattail_config.get_app()
 
        model = app.model
 
        person = model.Person(display_name="Fred Flintstone")
 
        user = model.User(username='fred', person=person)
 
        batch = model.InventoryBatch(id=42, created=self.app.localtime(),
 
@@ -466,10 +469,11 @@ class upgrade_failure(Email):
 
    default_subject = "Upgrade failure for ${system_title}"
 

	
 
    def sample_data(self, request):
 
        upgrade = Object(
 
        app = request.rattail_config.get_app()
 
        upgrade = app.make_object(
 
            description="upgrade to the latest!",
 
            notes="nothing special",
 
            executed=make_utc(),
 
            executed=app.make_utc(),
 
            executed_by="Fred Flintstone",
 
            exit_code=42,
 
        )
 
@@ -487,10 +491,11 @@ class upgrade_success(Email):
 
    default_subject = "Upgrade success for ${system_title}"
 

	
 
    def sample_data(self, request):
 
        upgrade = Object(
 
        app = request.rattail_config.get_app()
 
        upgrade = app.make_object(
 
            description="upgrade to the latest!",
 
            notes="nothing special",
 
            executed=make_utc(),
 
            executed=app.make_utc(),
 
            executed_by="Fred Flintstone",
 
            exit_code=0,
 
        )
rattail/importing/csv.py
Show inline comments
 
@@ -40,7 +40,7 @@ from rattail.importing.files import FromFile
 
from rattail.db.util import make_topo_sortkey
 

	
 

	
 
class FromCSVToSQLAlchemyMixin(object):
 
class FromCSVToSQLAlchemyMixin:
 

	
 
    host_key = 'csv'
 
    generic_host_title = "CSV"
 
@@ -215,7 +215,7 @@ class FromCSVToRattail(FromCSVToSQLAlchemyMixin, FromFileHandler, importing.ToRa
 

	
 
    def get_model(self):
 
        if self.config:
 
            return self.config.get_model()
 
            return self.app.model
 

	
 
        from rattail.db import model
 
        return model
rattail/importing/exporters.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2023 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -37,7 +37,7 @@ from rattail.importing.rattail import FromRattailHandler, FromRattail
 
from rattail.importing.handlers import ToCSVHandler
 

	
 

	
 
class FromSQLAlchemyToCSVMixin(object):
 
class FromSQLAlchemyToCSVMixin:
 

	
 
    # subclass must define this
 
    FromParent = None
 
@@ -99,7 +99,7 @@ class FromRattailToCSV(FromSQLAlchemyToCSVMixin, FromRattailHandler, ToCSVHandle
 

	
 
    def get_model(self):
 
        if self.config:
 
            return self.config.get_model()
 
            return self.app.model
 

	
 
        from rattail.db import model
 
        return model
 
@@ -112,7 +112,7 @@ class ToFile(Importer):
 
    empty_local_data = True
 

	
 
    def setup(self):
 
        super(ToFile, self).setup()
 
        super().setup()
 

	
 
        if not hasattr(self, 'output_file_path'):
 
            filename = self.get_output_filename()
 
@@ -121,7 +121,7 @@ class ToFile(Importer):
 
            self.open_output_file()
 

	
 
    def teardown(self):
 
        super(ToFile, self).teardown()
 
        super().teardown()
 

	
 
        if not self.dry_run:
 
            self.close_output_file()
rattail/importing/handlers.py
Show inline comments
 
@@ -94,7 +94,7 @@ class ImportHandler(object):
 
            self.app = self.config.get_app()
 
            self.enum = self.config.get_enum()
 
            try:
 
                self.model = self.config.get_model()
 
                self.model = self.app.model
 
            except ImportError:
 
                pass
 

	
 
@@ -198,7 +198,7 @@ class ImportHandler(object):
 
        from rattail.db import Session
 

	
 
        session = Session()
 
        model = self.config.get_model()
 
        model = self.app.model
 
        metadata = sa.MetaData(schema='batch', bind=session.bind)
 
        user = session.merge(kwargs['runas_user'])
 
        handler_spec = self.config.get('rattail.batch', 'importer.handler',
rattail/importing/importers.py
Show inline comments
 
@@ -125,7 +125,7 @@ class Importer(object):
 
        self.app = config.get_app() if config else None
 
        self.enum = config.get_enum() if config else None
 
        try:
 
            self.model = config.get_model() if config else None
 
            self.model = self.app.model if config else None
 
        except ImportError:
 
            pass
 
        self.model_class = kwargs.pop('model_class', self.get_model_class())
rattail/importing/rattail.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2023 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -29,7 +29,6 @@ from collections import OrderedDict
 

	
 
import sqlalchemy as sa
 

	
 
from rattail.db import Session
 
from rattail.importing import model
 
from rattail.importing.handlers import FromSQLAlchemyHandler, ToSQLAlchemyHandler
 
from rattail.importing.sqlalchemy import FromSQLAlchemySameToSame
 
@@ -50,7 +49,7 @@ class FromRattailHandler(FromSQLAlchemyHandler):
 
        return self.config.app_title(default="Rattail")
 

	
 
    def make_host_session(self):
 
        return Session()
 
        return self.app.make_session()
 

	
 

	
 
class ToRattailHandler(ToSQLAlchemyHandler):
 
@@ -68,14 +67,14 @@ class ToRattailHandler(ToSQLAlchemyHandler):
 
        kwargs = {}
 
        if hasattr(self, 'runas_user'):
 
            kwargs['continuum_user'] = self.runas_user
 
        return Session(**kwargs)
 
        return self.app.make_session(**kwargs)
 

	
 
    def begin_local_transaction(self):
 
        self.session = self.make_session()
 

	
 
        # load "runas user" into current session
 
        if hasattr(self, 'runas_user') and self.runas_user:
 
            dbmodel = self.config.get_model()
 
            dbmodel = self.app.model
 
            runas_user = self.session.get(dbmodel.User, self.runas_user.uuid)
 
            if not runas_user:
 
                log.info("runas_user does not exist in target session: %s",
 
@@ -195,7 +194,7 @@ class FromRattailToRattailImport(FromRattailToRattailBase, FromRattailHandler, T
 
        return self.config.node_title(default="Rattail (local)")
 

	
 
    def make_host_session(self):
 
        return Session(bind=self.config.rattail_engines[self.dbkey])
 
        return self.app.make_session(bind=self.config.rattail_engines[self.dbkey])
 

	
 

	
 
class FromRattailToRattailExport(FromRattailToRattailBase, FromRattailHandler, ToRattailHandler):
 
@@ -219,7 +218,7 @@ class FromRattailToRattailExport(FromRattailToRattailBase, FromRattailHandler, T
 
        return "{} ({})".format(self.config.app_title(default="Rattail"), self.dbkey)
 

	
 
    def make_session(self):
 
        return Session(bind=self.config.rattail_engines[self.dbkey])
 
        return self.app.make_session(bind=self.config.rattail_engines[self.dbkey])
 

	
 

	
 
class FromRattail(FromSQLAlchemySameToSame):
 
@@ -239,7 +238,7 @@ class GlobalPersonImporter(FromRattail, model.GlobalPersonImporter):
 
    """
 

	
 
    def query(self):
 
        query = super(GlobalPersonImporter, self).query()
 
        query = super().query()
 

	
 
        # never include "local only" people
 
        query = query.filter(sa.or_(
 
@@ -254,7 +253,7 @@ class GlobalPersonImporter(FromRattail, model.GlobalPersonImporter):
 
        if person.local_only:
 
            return
 

	
 
        data = super(GlobalPersonImporter, self).normalize_host_object(person)
 
        data = super().normalize_host_object(person)
 
        return data
 

	
 

	
 
@@ -283,7 +282,7 @@ class GlobalRoleImporter(RoleImporter):
 

	
 
    @property
 
    def supported_fields(self):
 
        fields = list(super(GlobalRoleImporter, self).supported_fields)
 
        fields = list(super().supported_fields)
 
        fields.extend([
 
            'permissions',
 
            'users',
 
@@ -297,7 +296,7 @@ class GlobalRoleImporter(RoleImporter):
 
        """
 
        Return the query to be used when caching "local" data.
 
        """
 
        query = super(GlobalRoleImporter, self).cache_query()
 
        query = super().cache_query()
 
        model = self.model
 

	
 
        # only want roles which are *meant* to be synced
 
@@ -306,7 +305,7 @@ class GlobalRoleImporter(RoleImporter):
 
        return query
 

	
 
    def query(self):
 
        query = super(GlobalRoleImporter, self).query()
 
        query = super().query()
 
        model = self.model
 

	
 
        # only want roles which are *meant* to be synced
 
@@ -323,7 +322,7 @@ class GlobalRoleImporter(RoleImporter):
 
        if not role.sync_me:
 
            return
 

	
 
        data = super(GlobalRoleImporter, self).normalize_local_object(role)
 
        data = super().normalize_local_object(role)
 
        if data:
 

	
 
            # users
 
@@ -340,9 +339,7 @@ class GlobalRoleImporter(RoleImporter):
 
            return data
 

	
 
    def update_object(self, role, host_data, local_data=None, **kwargs):
 
        role = super(GlobalRoleImporter, self).update_object(role, host_data,
 
                                                             local_data=local_data,
 
                                                             **kwargs)
 
        role = super().update_object(role, host_data, local_data=local_data, **kwargs)
 
        model = self.model
 

	
 
        # users
 
@@ -410,7 +407,7 @@ class GlobalUserImporter(FromRattail, model.GlobalUserImporter):
 
    """
 

	
 
    def query(self):
 
        query = super(GlobalUserImporter, self).query()
 
        query = super().query()
 

	
 
        # never include "local only" users
 
        query = query.filter(sa.or_(
 
@@ -425,7 +422,7 @@ class GlobalUserImporter(FromRattail, model.GlobalUserImporter):
 
        if user.local_only:
 
            return
 

	
 
        data = super(GlobalUserImporter, self).normalize_host_object(user)
 
        data = super().normalize_host_object(user)
 
        return data
 

	
 

	
 
@@ -433,12 +430,12 @@ class AdminUserImporter(FromRattail, model.AdminUserImporter):
 

	
 
    @property
 
    def supported_fields(self):
 
        return super(AdminUserImporter, self).supported_fields + [
 
        return super().supported_fields + [
 
            'admin',
 
        ]
 

	
 
    def normalize_host_object(self, user):
 
        data = super(AdminUserImporter, self).normalize_local_object(user) # sic
 
        data = super().normalize_local_object(user) # sic
 
        if 'admin' in self.fields: # TODO: do we really need this, after the above?
 
            data['admin'] = self.admin_uuid in [r.role_uuid for r in user._roles]
 
        return data
 
@@ -578,7 +575,7 @@ class ProductWithPriceImporter(FromRattail, model.ProductImporter):
 
    ]
 

	
 
    def query(self):
 
        query = super(ProductWithPriceImporter, self).query()
 
        query = super().query()
 

	
 
        # make sure potential unit items (i.e. rows with NULL unit_uuid) come
 
        # first, so they will be created before pack items reference them
 
@@ -609,7 +606,7 @@ class ProductImporter(ProductWithPriceImporter):
 

	
 
    @property
 
    def simple_fields(self):
 
        fields = super(ProductImporter, self).simple_fields
 
        fields = super().simple_fields
 
        # NOTE: it seems we can't consider these "simple" due to the
 
        # self-referencing foreign key situation.  an importer can still
 
        # "support" these fields, but they're excluded from the simple set for
 
@@ -657,7 +654,7 @@ class ProductImageImporter(FromRattail, model.ProductImageImporter):
 
class LabelProfileImporter(FromRattail, model.LabelProfileImporter):
 

	
 
    def query(self):
 
        query = super(LabelProfileImporter, self).query()
 
        query = super().query()
 

	
 
        if not self.config.getbool('rattail', 'labels.sync_all_profiles', default=False):
 
            # only fetch labels from host which are marked as "sync me"
rattail/importing/shopfoo.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2020 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -24,23 +24,20 @@
 
Rattail -> Rattail "local" data import, for sake of Shopfoo pattern data
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
from sqlalchemy import orm
 

	
 
from rattail.db import model
 
from rattail.core import get_uuid
 
from rattail.db.model import Product
 

	
 

	
 
class ShopfooProductImporterMixin(object):
 
class ShopfooProductImporterMixin:
 
    """
 
    Product -> ShopfooProduct
 
    """
 
    host_model_class = model.Product
 
    host_model_class = Product
 
    key = 'uuid'
 

	
 
    def setup(self):
 
        super(ShopfooProductImporterMixin, self).setup()
 
        super().setup()
 
        self.cache_shopfoo_products()
 

	
 
    def cache_shopfoo_products(self):
 
@@ -48,6 +45,7 @@ class ShopfooProductImporterMixin(object):
 
                                                 key='product_uuid')
 

	
 
    def query(self):
 
        model = self.app.model
 
        return self.host_session.query(model.Product)\
 
                                .options(orm.joinedload(model.Product.brand))\
 
                                .options(orm.joinedload(model.Product.regular_price))
 
@@ -56,6 +54,7 @@ class ShopfooProductImporterMixin(object):
 
        if hasattr(self, 'shopfoo_products'):
 
            return self.shopfoo_products.get(product.uuid)
 

	
 
        model = self.app.model
 
        try:
 
            return self.session.query(self.model_class)\
 
                               .filter(self.model_class.product == product)\
 
@@ -71,7 +70,7 @@ class ShopfooProductImporterMixin(object):
 
            return shopfoo_product.uuid
 

	
 
        # otherwise we must make a new one, which means a new record
 
        return get_uuid()
 
        return self.app.make_uuid()
 

	
 
    def normalize_host_object(self, product):
 

	
rattail/mail.py
Show inline comments
 
@@ -93,7 +93,7 @@ class EmailHandler(object):
 
        except ImportError:
 
            pass
 
        else:
 
            self.model = self.config.get_model()
 
            self.model = self.app.model
 

	
 
    def use_entry_points(self):
 
        """
 
@@ -313,6 +313,7 @@ class EmailHandler(object):
 
        context['rattail_config'] = self.config
 
        context['app'] = self.app
 
        context['app_title'] = self.config.app_title(default="Rattail")
 
        # TODO: deprecate / remove this
 
        context['localtime'] = localtime
 
        return context
 

	
 
@@ -416,7 +417,7 @@ class Email(object):
 
        except ImportError:
 
            pass
 
        else:
 
            self.model = self.config.get_model()
 
            self.model = self.app.model
 

	
 
        if key:
 
            self.key = key
rattail/monitoring.py
Show inline comments
 
@@ -35,7 +35,7 @@ import subprocess
 
log = logging.getLogger(__name__)
 

	
 

	
 
class MonitorAction(object):
 
class MonitorAction:
 
    """
 
    Base class for monitor actions.  Note that not all actions are
 
    class-based, but the ones which are should probably inherit from
 
@@ -47,7 +47,7 @@ class MonitorAction(object):
 
        self.app = config.get_app()
 
        self.enum = config.get_enum()
 
        try:
 
            self.model = config.get_model()
 
            self.model = self.app.model
 
        except ImportError:
 
            pass
 

	
rattail/problems/reports.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2023 Lance Edgar
 
#  Copyright © 2010-2024 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -24,10 +24,8 @@
 
Problem Reports Framework
 
"""
 

	
 
from rattail.util import progress_loop
 

	
 

	
 
class ProblemReport(object):
 
class ProblemReport:
 
    """
 
    Base class for all problem reports.
 

	
 
@@ -46,12 +44,12 @@ class ProblemReport(object):
 
        self.dry_run = dry_run
 
        self.progress = progress
 
        self.app = self.config.get_app()
 
        self.model = self.config.get_model()
 
        self.model = self.app.model
 
        self.enum = self.config.get_enum()
 

	
 
    def progress_loop(self, func, items, factory=None, **kwargs):
 
        factory = factory or self.progress
 
        return progress_loop(func, items, factory, **kwargs)
 
        return self.app.progress_loop(func, items, factory, **kwargs)
 

	
 
    def cache_model(self, session, model, **kwargs):
 
        """
rattail/reporting/reports.py
Show inline comments
 
@@ -60,8 +60,8 @@ class Report:
 
    def __init__(self, config, **kwargs):
 
        self.config = config
 
        self.app = config.get_app()
 
        self.model = self.app.model
 
        self.enum = config.get_enum()
 
        self.model = config.get_model()
 
        for key, value in kwargs.items():
 
            setattr(self, key, value)
 

	
rattail/vendors/catalogs.py
Show inline comments
 
@@ -61,7 +61,7 @@ class CatalogParser(object):
 
            self.enum = config.get_enum()
 

	
 
            try:
 
                self.model = config.get_model()
 
                self.model = self.app.model
 
            except ImportError:
 
                pass # sqlalchemy not installed
 

	
rattail/vendors/invoices.py
Show inline comments
 
@@ -41,8 +41,8 @@ class InvoiceParser(object):
 
    def __init__(self, config):
 
        self.config = config
 
        self.app = config.get_app()
 
        self.model = self.app.model
 
        self.enum = config.get_enum()
 
        self.model = config.get_model()
 

	
 
    @property
 
    def key(self):
tests/autocomplete/test_base.py
Show inline comments
 
@@ -13,6 +13,7 @@ class TestAutocompleter(TestCase):
 

	
 
    def setUp(self):
 
        self.config = self.make_config()
 
        self.app = self.config.get_app()
 

	
 
    def make_config(self):
 
        return make_config([], extend=False)
 
@@ -32,7 +33,7 @@ class TestAutocompleter(TestCase):
 

	
 
    def test_get_model_class(self):
 
        try:
 
            model = self.config.get_model()
 
            model = self.app.model
 
        except ImportError:
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
@@ -73,7 +74,7 @@ class TestAutocompleter(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 

	
 
@@ -132,6 +133,7 @@ class TestPhoneMagicMixin(TestCase):
 

	
 
    def setUp(self):
 
        self.config = self.make_config()
 
        self.app = self.config.get_app()
 

	
 
    def make_config(self):
 
        return make_config([], extend=False)
 
@@ -143,7 +145,7 @@ class TestPhoneMagicMixin(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 

	
tests/autocomplete/test_brands.py
Show inline comments
 
@@ -16,6 +16,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
        def make_config(self):
 
@@ -26,7 +27,7 @@ else:
 

	
 
        def test_autocomplete(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
tests/autocomplete/test_customers.py
Show inline comments
 
@@ -16,6 +16,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
        def make_config(self):
 
@@ -26,7 +27,7 @@ else:
 

	
 
        def test_autocomplete(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 
            enum = self.config.get_enum()
tests/autocomplete/test_departments.py
Show inline comments
 
@@ -16,6 +16,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
        def make_config(self):
 
@@ -26,7 +27,7 @@ else:
 

	
 
        def test_autocomplete(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
tests/autocomplete/test_employees.py
Show inline comments
 
@@ -17,6 +17,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
        def make_config(self):
 
@@ -27,7 +28,7 @@ else:
 

	
 
        def test_autocomplete(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 
            enum = self.config.get_enum()
tests/autocomplete/test_people.py
Show inline comments
 
@@ -16,6 +16,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
        def make_config(self):
 
@@ -26,7 +27,7 @@ else:
 

	
 
        def test_autocomplete(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 
            enum = self.config.get_enum()
 
@@ -59,6 +60,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
        def make_config(self):
 
@@ -69,7 +71,7 @@ else:
 

	
 
        def test_autocomplete(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 
            enum = self.config.get_enum()
tests/autocomplete/test_products.py
Show inline comments
 
@@ -16,10 +16,11 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
            self.engine = sa.create_engine('sqlite://')
 
            self.model = self.config.get_model()
 
            self.model = self.app.model
 
            self.model.Base.metadata.create_all(bind=self.engine)
 
            self.session = Session(bind=self.engine)
 

	
tests/autocomplete/test_vendors.py
Show inline comments
 
@@ -16,6 +16,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.autocompleter = self.make_autocompleter()
 

	
 
        def make_config(self):
 
@@ -26,7 +27,7 @@ else:
 

	
 
        def test_autocomplete(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
tests/batch/test_handheld.py
Show inline comments
 
@@ -16,6 +16,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.handler = self.make_handler()
 

	
 
        def make_config(self):
 
@@ -26,7 +27,7 @@ else:
 

	
 
        def test_make_inventory_batch(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -43,7 +44,7 @@ else:
 

	
 
        def test_make_label_batch(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
tests/batch/test_handlers.py
Show inline comments
 
@@ -17,6 +17,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.handler = self.make_handler()
 

	
 
        def make_config(self):
 
@@ -27,7 +28,7 @@ else:
 

	
 
        def test_consume_batch_id(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -41,7 +42,7 @@ else:
 

	
 
        def test_get_effective_rows(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
tests/batch/test_product.py
Show inline comments
 
@@ -16,6 +16,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.handler = self.make_handler()
 

	
 
        def make_config(self):
 
@@ -26,7 +27,7 @@ else:
 

	
 
        def test_make_label_batch(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -43,7 +44,7 @@ else:
 

	
 
        def test_make_pricing_batch(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
tests/batch/test_vendorcatalog.py
Show inline comments
 
@@ -21,6 +21,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.handler = self.make_handler()
 

	
 
        def make_config(self):
 
@@ -43,7 +44,7 @@ else:
 

	
 
        def test_populate_from_file(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 
            app = self.config.get_app()
 
@@ -93,7 +94,7 @@ else:
 

	
 
        def test_identify_product(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 
            app = self.config.get_app()
 
@@ -142,7 +143,7 @@ else:
 

	
 
        def test_refresh_row(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 
            app = self.config.get_app()
tests/commands/test_typer.py
Show inline comments
 
@@ -29,13 +29,14 @@ class TestMakeCliConfig(FileConfigTestCase):
 
    def test_bad_model(self):
 
        myconf = self.write_file('my.conf', """
 
[rattail]
 
model = invalid_model_spec
 
model_spec = invalid_model_spec
 
""")
 
        ctx = MagicMock(params={
 
            'config_paths': [myconf],
 
        })
 
        config = mod.make_cli_config(ctx)
 
        self.assertRaises(ImportError, config.get_model)
 
        app = config.get_app()
 
        self.assertRaises(ImportError, getattr, app, 'model')
 

	
 
    def test_versioning(self):
 
        # nb. must specify config file to avoid any files in dev environment
tests/db/test_changes.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
# -*- coding: utf-8; -*-
 

	
 
from unittest import TestCase
 
from unittest.mock import patch, DEFAULT, Mock, MagicMock, call
 

	
 
from rattail.config import RattailConfig
 
from rattail.core import get_uuid
 

	
 
try:
 
    from sqlalchemy import orm
 
@@ -19,6 +18,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = RattailConfig()
 
            self.app = self.config.get_app()
 

	
 
        def test_session_class(self):
 
            Session = orm.sessionmaker()
 
@@ -79,6 +79,7 @@ else:
 

	
 
        def extra_setup(self):
 
            self.config = RattailConfig()
 
            self.app = self.config.get_app()
 

	
 
        def test_ignore_object(self):
 
            recorder = changes.ChangeRecorder(self.config)
 
@@ -126,7 +127,7 @@ else:
 
            for Model in (model.PersonEmailAddress, model.PersonPhoneNumber, model.PersonMailingAddress):
 

	
 
                self.assertEqual(self.session.query(model.Change).count(), 0)
 
                uuid = get_uuid()
 
                uuid = self.app.make_uuid()
 
                obj = Model(uuid=uuid, person=person)
 
                recorder.process_deleted_object(self.session, obj)
 
                self.assertEqual(self.session.query(model.Change).count(), 2)
 
@@ -145,7 +146,7 @@ else:
 
            for Model in (model.EmployeeStore, model.EmployeeDepartment):
 

	
 
                self.assertEqual(self.session.query(model.Change).count(), 0)
 
                uuid = get_uuid()
 
                uuid = self.app.make_uuid()
 
                obj = Model(uuid=uuid, employee=employee)
 
                recorder.process_deleted_object(self.session, obj)
 
                self.assertEqual(self.session.query(model.Change).count(), 2)
 
@@ -261,55 +262,58 @@ else:
 
        #     session.get.return_value = None
 
        #     self.assertTrue(recorder.record_change(session, model.Product()))
 

	
 
        @patch.multiple('rattail.db.changes', get_uuid=DEFAULT, object_mapper=DEFAULT)
 
        def test_ensure_uuid(self, get_uuid, object_mapper):
 
        @patch.multiple('rattail.db.changes', object_mapper=DEFAULT)
 
        def test_ensure_uuid(self, object_mapper):
 
            recorder = changes.ChangeRecorder(self.config)
 
            uuid_column = Mock()
 
            object_mapper.return_value.columns.__getitem__.return_value = uuid_column
 

	
 
            # uuid already present
 
            product = model.Product(uuid='some_uuid')
 
            recorder.ensure_uuid(product)
 
            self.assertEqual(product.uuid, 'some_uuid')
 
            self.assertFalse(get_uuid.called)
 

	
 
            # no uuid yet, auto-generate
 
            uuid_column.foreign_keys = False
 
            get_uuid.return_value = 'another_uuid'
 
            product = model.Product()
 
            self.assertTrue(product.uuid is None)
 
            recorder.ensure_uuid(product)
 
            get_uuid.assert_called_once_with()
 
            self.assertEqual(product.uuid, 'another_uuid')
 

	
 
            # some heavy mocking for following tests
 
            uuid_column.foreign_keys = True
 
            remote_side = MagicMock(key='uuid')
 
            prop = MagicMock(__class__=orm.RelationshipProperty, key='foreign_thing')
 
            prop.remote_side.__len__.return_value = 1
 
            prop.remote_side.__iter__.return_value = [remote_side]
 
            object_mapper.return_value.iterate_properties.__iter__.return_value = [prop]
 

	
 
            # uuid fetched from existing foreign key object
 
            get_uuid.reset_mock()
 
            instance = Mock(uuid=None, foreign_thing=Mock(uuid='secondary_uuid'))
 
            recorder.ensure_uuid(instance)
 
            self.assertFalse(get_uuid.called)
 
            self.assertEqual(instance.uuid, 'secondary_uuid')
 

	
 
            # foreign key object doesn't exist; uuid generated as fallback
 
            get_uuid.return_value = 'fallback_uuid'
 
            instance = Mock(uuid=None, foreign_thing=None)
 
            recorder.ensure_uuid(instance)
 
            get_uuid.assert_called_once_with()
 
            self.assertEqual(instance.uuid, 'fallback_uuid')
 
            with patch.object(recorder.app, 'make_uuid') as make_uuid:
 

	
 
                # uuid already present
 
                product = model.Product(uuid='some_uuid')
 
                recorder.ensure_uuid(product)
 
                self.assertEqual(product.uuid, 'some_uuid')
 
                self.assertFalse(make_uuid.called)
 

	
 
                # no uuid yet, auto-generate
 
                uuid_column.foreign_keys = False
 
                make_uuid.return_value = 'another_uuid'
 
                product = model.Product()
 
                self.assertTrue(product.uuid is None)
 
                recorder.ensure_uuid(product)
 
                make_uuid.assert_called_once_with()
 
                self.assertEqual(product.uuid, 'another_uuid')
 

	
 
                # some heavy mocking for following tests
 
                uuid_column.foreign_keys = True
 
                remote_side = MagicMock(key='uuid')
 
                prop = MagicMock(__class__=orm.RelationshipProperty, key='foreign_thing')
 
                prop.remote_side.__len__.return_value = 1
 
                prop.remote_side.__iter__.return_value = [remote_side]
 
                object_mapper.return_value.iterate_properties.__iter__.return_value = [prop]
 

	
 
                # uuid fetched from existing foreign key object
 
                make_uuid.reset_mock()
 
                instance = Mock(uuid=None, foreign_thing=Mock(uuid='secondary_uuid'))
 
                recorder.ensure_uuid(instance)
 
                self.assertFalse(make_uuid.called)
 
                self.assertEqual(instance.uuid, 'secondary_uuid')
 

	
 
                # foreign key object doesn't exist; uuid generated as fallback
 
                make_uuid.return_value = 'fallback_uuid'
 
                instance = Mock(uuid=None, foreign_thing=None)
 
                recorder.ensure_uuid(instance)
 
                make_uuid.assert_called_once_with()
 
                self.assertEqual(instance.uuid, 'fallback_uuid')
 

	
 

	
 
    class TestFunctionalChanges(DataTestCase):
 

	
 
        def setUp(self):
 
            super(TestFunctionalChanges, self).setUp()
 
            changes.record_changes(self.session)
 
            super().setUp()
 
            self.config = RattailConfig()
 
            changes.record_changes(self.session, config=self.config)
 

	
 
        def test_add(self):
 
            product = model.Product()
tests/test_app.py
Show inline comments
 
@@ -118,7 +118,7 @@ class TestAppHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 

	
 
@@ -532,7 +532,7 @@ class TestAppHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 

	
 
@@ -561,7 +561,7 @@ class TestAppHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        
 
        # default behavior should "work" albeit with no engine bound,
 
@@ -611,7 +611,7 @@ class TestAppHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 

	
tests/test_auth.py
Show inline comments
 
@@ -18,6 +18,7 @@ else:
 

	
 
        def setUp(self):
 
            self.config = self.make_config()
 
            self.app = self.config.get_app()
 
            self.handler = self.make_handler()
 

	
 
        def make_config(self):
 
@@ -28,7 +29,7 @@ else:
 

	
 
        def test_authenticate_user(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -46,7 +47,7 @@ else:
 

	
 
        def test_generate_preferred_username(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -61,7 +62,7 @@ else:
 

	
 
        def test_generate_unique_username(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -89,7 +90,7 @@ else:
 

	
 
        def test_make_user(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -110,7 +111,7 @@ else:
 

	
 
        def test_delete_user(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -125,7 +126,7 @@ else:
 

	
 
        def test_has_permission(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -152,7 +153,7 @@ else:
 

	
 
        def test_get_permissions(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -216,7 +217,7 @@ else:
 

	
 
        def test_grant_permission(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
 
@@ -232,7 +233,7 @@ else:
 

	
 
        def test_revoke_permission(self):
 
            engine = sa.create_engine('sqlite://')
 
            model = self.config.get_model()
 
            model = self.app.model
 
            model.Base.metadata.create_all(bind=engine)
 
            session = Session(bind=engine)
 

	
tests/test_config.py
Show inline comments
 
@@ -184,13 +184,13 @@ require = %(here)s/first.conf
 
        except ImportError:
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        myconfig = config.RattailConfig()
 

	
 
        # default is rattail.db.model
 
        myconfig = config.RattailConfig()
 
        model = myconfig.get_model()
 
        self.assertIs(model, sys.modules['rattail.db.model'])
 

	
 
        # or config may specify
 
        myconfig = config.RattailConfig()
 
        myconfig.setdefault('rattail.model', 'rattail.trainwreck.db.model')
 
        model = myconfig.get_model()
 
        self.assertIs(model, sys.modules['rattail.trainwreck.db.model'])
tests/test_products.py
Show inline comments
 
@@ -14,6 +14,7 @@ class TestProductsHandler(TestCase):
 

	
 
    def setUp(self):
 
        self.config = self.make_config()
 
        self.app = self.config.get_app()
 
        self.handler = self.make_handler()
 

	
 
    def make_config(self):
 
@@ -49,7 +50,7 @@ class TestProductsHandler(TestCase):
 

	
 
    def test_make_full_description(self):
 
        try:
 
            model = self.config.get_model()
 
            model = self.app.model
 
        except ImportError:
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
@@ -83,7 +84,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 

	
 
@@ -109,7 +110,7 @@ class TestProductsHandler(TestCase):
 

	
 
    def test_get_url(self):
 
        try:
 
            model = self.config.get_model()
 
            model = self.app.model
 
        except ImportError:
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
@@ -134,7 +135,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -163,7 +164,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -198,7 +199,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -238,7 +239,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -296,7 +297,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -323,7 +324,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -350,7 +351,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -382,7 +383,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -410,7 +411,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
 
@@ -438,7 +439,7 @@ class TestProductsHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
tests/vendors/test_catalogs.py
Show inline comments
 
@@ -12,6 +12,7 @@ class TestCatalogParser(TestCase):
 

	
 
    def setUp(self):
 
        self.config = self.make_config()
 
        self.app = self.config.get_app()
 
        self.parser = self.make_parser()
 

	
 
    def make_config(self):
 
@@ -27,7 +28,7 @@ class TestCatalogParser(TestCase):
 

	
 
    def test_make_row(self):
 
        try:
 
            model = self.config.get_model()
 
            model = self.app.model
 
        except ImportError:
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
tests/vendors/test_handler.py
Show inline comments
 
@@ -14,6 +14,7 @@ class TestVendorHandler(TestCase):
 

	
 
    def setUp(self):
 
        self.config = self.make_config()
 
        self.app = self.config.get_app()
 
        self.handler = self.make_handler()
 

	
 
    def make_config(self):
 
@@ -41,7 +42,7 @@ class TestVendorHandler(TestCase):
 
            pytest.skip("test is not relevant without sqlalchemy")
 

	
 
        engine = sa.create_engine('sqlite://')
 
        model = self.config.get_model()
 
        model = self.app.model
 
        model.Base.metadata.create_all(bind=engine)
 
        session = Session(bind=engine)
 
        app = self.config.get_app()
0 comments (0 inline, 0 general)