Commits

Alexander Solovyov committed f4e5765

switch to amalgam

Comments (0)

Files changed (21)

 Werkzeug
 Jinja2 > 2.4
-SQLAlchemy 
 WTForms
+amalgam
     # Static data included by MANIFEST.in
     include_package_data=True,
 
-    install_requires=['Jinja2', 'Werkzeug', 'opster'],
+    install_requires=['Jinja2', 'Werkzeug', 'opster', 'amalgam'],
 
     entry_points='''
     [console_scripts]

svarga/__init__.py

 from svarga.core.env import env
+from svarga.core.database import db

svarga/apps/__init__.py

         if app_list is None:
             raise ImproperlyConfigured('Missing %s setting' % CONFIG_APP_LIST)
 
+        if isinstance(app_list, basestring):
+            raise ImproperlyConfigured('%s is a string, not a list: maybe you '
+                                       'forgot to put a comma in tuple?'
+                                       % CONFIG_APP_LIST)
+
         # Call app_init of the loaded apps, so applications have chance
         # to contribute extra initializers to the settings class
         # Basically, it is very similar to the APPS_INIT config setting
             self.apps[name] = settings
 
         # Verify application dependencies
-        for app_name,app in self.apps.iteritems():
+        for app_name, app in self.apps.iteritems():
             for dependency in app.dependencies:
                 if dependency not in self.apps:
                     raise Exception("Application '%s' requires '%s' "

svarga/bootstrap/project_template/manage.py

 if not op.exists(act):
     import sys
     sys.stderr.write("Write: your virtualenv is not ready. "
-                     "Run ./buildenv.py to fix that.")
+                     "Run ./buildenv.py to fix that.\n")
     sys.exit(1)
 execfile(act, {'__file__': act})
 

svarga/bootstrap/project_template/settings.py_tmpl

 
 URLCONF = '%(projname)s.urls.urls'
 
-# SQL Alchemy settings
-SQLA_URL = 'sqlite:///' + op.join(ROOT, 'data.sqlite')
-SQLA_ENGINE_SETTINGS = {'echo': False}
+DATABASES = {'default': ('sqla', 'sqlite:///' + op.join(ROOT, 'data.sqlite'),
+                         {'engine_options': {'echo': False}})}
 
 # Your secret key
-SECRET_KEY = %(secretkey)s
+SECRET_KEY = %(secretkey)r
 
 # Applications
 LOCAL_APPS = (

svarga/bootstrap/svarga_admin.py

     shutil.move(op.join(name, 'projname'),
                 op.join(name, name))
 
-    key = repr(''.join(random.choice(KEY_CHARS) for _ in xrange(KEY_LENGTH)))
+    key = ''.join(random.choice(KEY_CHARS) for _ in xrange(KEY_LENGTH))
     process_files(name, secretkey=key, projname=name)
     chmodx(op.join(name, 'manage.py'))
     chmodx(op.join(name, 'buildenv.py'))

svarga/core/commands.py

 from werkzeug import SharedDataMiddleware
 from opster import command
 
-from svarga import env
+from svarga import env, db
 from svarga.core.handler import SvargaHandler
 
-# TODO: Move related commands to models.commands
-from svarga.db import models
-
 log = logging.getLogger(__name__)
 
 @command(usage='[-a IP] [-p PORT]')
 def syncdb():
     '''Synchronize database structure
     '''
-    models.sync_db()
+    for v in db.databases.itervalues():
+        if hasattr(v, 'create_all'):
+            v.create_all()
 
 
 # Declare options only if they are different from --option=value

svarga/core/conf/local_settings.py

 LOCAL_INIT = [
     'svarga.apps.LocalSettingsProvider',
     'svarga.template.LocalSettingsProvider',
+    'svarga.core.database.database_local_init',
     ]
 
 # Application initializers
 APPS_INIT = [
-    'svarga.db.backend.contribute',
     'svarga.template.apps.contribute',
     ]
 
-MODEL_BACKENDS = [
-    'svarga.db.sqla.backend.DbBackend',
-    ]
-
 # Enabled template loaders
 TEMPLATE_LOADERS = [
     'svarga.template.loaders.PathLoader',

svarga/core/database.py

+import amalgam
+
+from svarga.core.exceptions import ImproperlyConfigured
+from svarga.utils.imports import import_module
+from svarga.utils.middleware import middleware
+
+
+class NotDeclared(object):
+    def __getattr__(self, name):
+        raise AttributeError("Database 'default' is not declared")
+
+
+class Database(object):
+    databases = None
+
+    def __init__(self, databases=None):
+        '''Create a database holder object
+
+        For information on ``databases`` argument see :py:meth:`configure`
+        '''
+        self.databases = {}
+        if databases:
+            self.configure(databases)
+
+    def configure(self, databases):
+        '''Initialize databases.
+
+        ``databases`` argument should be in format::
+
+          {'default': ('dbengine', 'uri', {'other': 'parameters'}),
+           'dbname': ('dbengine', 'uri')}
+
+        Name ``default`` has special meaning - if you're trying to get some
+        attribute from this object, it will be taken from database with name
+        ``default``.
+        '''
+        def getopts(opts):
+            if len(opts) == 3:
+                return opts[:2], opts[2]
+            else:
+                return opts, {}
+
+        self.databases = {}
+        databases = databases.copy()
+
+        if 'default' in databases:
+            opts, kwargs = getopts(databases.pop('default'))
+            self.databases['default'] = self.default
+        else:
+            self.databases['default'] = NotDeclared()
+
+        for name, opts in databases.items():
+            if hasattr(self.default, name):
+                raise ImproperlyConfigured(
+                    "%r is a name of attribute on a database 'default'")
+            opts, kwargs = getopts(opts)
+            self.databases[name] = amalgam.amalgam(*opts, **kwargs)
+
+    def __getattr__(self, name):
+        if name in self.databases:
+            return self.databases[name]
+        return getattr(self.default, name)
+
+    def __call__(self, *args, **kwargs):
+        return self.default(*args, **kwargs)
+
+
+@middleware
+def autocommit(request, view, kwargs, nexthandler):
+    def execute(action):
+        for v in db.databases.itervalues():
+            if hasattr(v, action):
+                getattr(v, action)()
+
+    try:
+        response = nexthandler(request, view, kwargs)
+        execute('commit')
+    except:
+        execute('rollback')
+        raise
+    finally:
+        execute('close')
+    return response
+
+
+db = Database()
+
+
+def database_local_init(env_class):
+    db.configure(getattr(env_class.settings, 'DATABASES', {}))
+    env_class.middlewares.insert(0, autocommit)
+    for app in env_class.apps:
+        import_module(app + '.models', required=False)

svarga/core/env.py

 
         # Define new environment class
         class Env(object):
-            # Environment processors
-            env_middleware = []
             # Request processing middlewares
             middlewares = _middlewares
             # Settings
 
         # Call local initializers
         local_init = map(import_attribute, getattr(s, 'LOCAL_INIT', ()))
-        s._local_init = map(lambda m: m(Env), local_init)
+        for init in local_init:
+            init(Env)
 
     @classmethod
     def get_class(cls):
         e.request = request
         local.env = e
 
-        # Run environment middleware
-        for mw in e.env_middleware:
-            mw.process_environment()
-
     @classmethod
     def register_static_url(cls, name, url, path):
         ec = cls.get_class()

svarga/db/__init__.py

Empty file removed.

svarga/db/backend.py

-from svarga.utils.imports import import_attribute
-
-class DbBackends(object):
-    _backends = {}
-    _instances = {}
-    _default = None
-
-    def __init__(self, apps, env_class):
-        for b in self._backends.itervalues():
-            self._instances[b.name] = b(apps, env_class)
-
-    @classmethod
-    def initialize(cls, env_class):
-        backends = getattr(env_class.settings, 'MODEL_BACKENDS', [])
-        backends_init = map(import_attribute, backends)
-
-        for b in backends_init:
-            if b.name in cls._backends:
-                continue
-
-            cls._backends[b.name] = b
-
-            if cls._default is None:
-                cls._default = b
-
-    # Backend stuff
-    @classmethod
-    def get_backend_class(cls, name):
-        if name is None:
-            return cls._default
-
-        try:
-            return cls._backends.get(name)
-        except KeyError:
-            raise KeyError('DbBackend %s was not found' % name)
-
-    @classmethod
-    def get_backend_instances(cls):
-        return cls._instances.itervalues()
-
-def contribute(app_settings, env_class):
-    """Registers any required properties in app settings class and returns
-    class for local configuration to use.
-    """
-    # Initialize list of backends
-    DbBackends.initialize(env_class)
-    return DbBackends

svarga/db/metabase.py

-class ModelMetaData(object):
-    def __init__(self, field_type, field, name,
-                 nullable=True, max_length=None, relation=False,
-                 primary_key=False, foreign_key=False,
-                 default=None, unique=False, **kwargs):
-        self.field_type = field_type
-        self.field = field
-        self.name = name
-        self.nullable = nullable
-        self.max_length = max_length
-
-        self.relation = relation
-        self.primary_key = primary_key
-        self.foreign_key = foreign_key
-
-        self.default = default
-        self.unique = unique
-
-        self.props = kwargs
-
-    def __repr__(self):
-        return '%s column metadata' % (self.field_type)

svarga/db/models.py

-from svarga.db.backend import DbBackends
-
-class BaseModelManager(object):
-    'Default model manager implementation'
-    def __init__(self, model):
-        self._model = model
-
-    def add(self, model):
-        """Register model with session. If model is not added, it won't
-        be saved in the database
-        """
-        raise NotImplemented()
-
-    def delete(self, model):
-        '''Delete model instance'''
-        raise NotImplemented()
-
-    def all(self):
-        raise NotImplemented()
-
-    def first(self):
-        raise NotImplemented()
-
-    def one(self):
-        raise NotImplemented()
-
-    def get(self, *args, **kwargs):
-        raise NotImplemented()
-
-    def filter(self, arg):
-        raise NotImplemented()
-
-    def filter_by(self, **kwargs):
-        raise NotImplemented()
-
-    def order_by(self, arg):
-        raise NotImplemented()
-
-    def limit(self, limit):
-        raise NotImplemented()
-
-    def offset(self, offset):
-        raise NotImplemented()
-
-    def count(self):
-        raise NotImplemented()
-
-# Model factory
-def factory(table_prefix=None, backend=None):
-    try:
-        return DbBackends.get_backend_class(backend).get_factory(table_prefix)
-    except TypeError, ex:
-        raise TypeError('Database backend %s was not found' % (backend or
-                                                               'Default'), ex)
-# ModelManager factory
-def get_model_manager(backend=None):
-    try:
-        return DbBackends.get_backend_class(backend).get_model_manager()
-    except TypeError, ex:
-        raise TypeError('Database backend %s was not found' % (backend or
-                                                               'Default'), ex)
-
-def sync_db():
-    "sync_db() shortcut"
-    for f in DbBackends.get_backend_instances():
-        f.sync_db()

svarga/db/sqla/__init__.py

Empty file removed.

svarga/db/sqla/backend.py

-from sqlalchemy import create_engine, MetaData
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.ext import declarative
-
-from svarga import env
-from svarga.core.metadata import create_metadata
-from svarga.utils.imports import import_module
-from svarga.utils.middleware import middleware
-from svarga.db.sqla import manager, process, meta, columns
-
-
-@middleware
-def autocommit(request, view, kwargs, nexthandler):
-    try:
-        response = nexthandler(request, view, kwargs)
-        env.sqla.session.commit()
-    except:
-        env.sqla.session.rollback()
-        raise
-    finally:
-        env.sqla.session.close()
-    return response
-
-
-class SQLAConfig(object):
-    "SQLAlchemy configuration object. Exposed in the environment"
-    def __init__(self, manager, engine, session):
-        self.manager = manager
-        self.engine = engine
-        self.session = session
-
-class SvargaDeclarativeMeta(declarative.DeclarativeMeta):
-    "Svarga models metaclass"
-    def __init__(cls, classname, bases, fields):
-        # Create or derive metadata
-        create_metadata(cls, bases, fields)
-
-        # Preprocess model
-        bases = process.preprocess(cls, bases, fields)
-
-        # Contribue model metadata
-        cls.Meta.updaters.append(meta.updater)
-
-        # Override bases
-        cls.__bases__ = bases
-
-        # Generate class
-        result = declarative.DeclarativeMeta.__init__(cls, classname,
-                                                      bases, fields)
-
-        # Create model manager
-        m = getattr(cls.Meta, 'model_manager', None) or manager.ModelManager
-        cls.objects = m(cls)
-
-        # Create repr, str and unicode mappings
-        cls.__repr__ = lambda self: '<%s: %s>' % (self.__class__.__name__,
-                                                  str(self))
-        cls.__str__ = lambda self: unicode(self).encode('utf-8')
-        if not hasattr(cls, '__unicode__'):
-            cls.__unicode__ = lambda self: u'0x%s' % self.__hash__()
-
-        # Add to the backends model list
-        DbBackend.add_model(cls)
-
-        return result
-
-class DbBackend(object):
-    """Application configuration manager for models.
-    Each local configuration will have own copy of the object, so
-    it is safe to store local data in the object.
-    """
-
-    name = 'sqla'
-
-    _models = set()
-    _metadata = MetaData()
-
-    # Constructor
-    def __init__(self, apps, env_class):
-        # Initialize application models, if required
-        for app, config in apps.iteritems():
-            if not hasattr(config, '_models'):
-                config._models = set()
-
-            # Try loading models from app's models.py
-            app_models = app + '.models'
-            import_module(app_models, required=False)
-
-        # Postprocess models
-        for m in self._models:
-            process.postprocess(m)
-            m.Meta.update()
-
-        # Initialize SQLA engine
-        self.engine = self._create_engine(env_class.settings)
-
-        # Initialize sessionmaker
-        self.session_maker = sessionmaker(bind=self.engine)
-
-        # Register as request middleware
-        env_class.env_middleware.append(self)
-        env_class.middlewares.insert(0, autocommit)
-
-    def _create_engine(self, settings):
-        """Create SQLAlchemy engine.
-
-        Overridable, in case if you want to override some of the settings."""
-        extra_settings = getattr(settings, 'SQLA_ENGINE_SETTINGS', {})
-        return create_engine(settings.SQLA_URL, **extra_settings)
-
-    # Middleware methods
-    def process_environment(self):
-        # Create session object
-        env.sqla = SQLAConfig(self, self.engine, self.session_maker())
-
-    # DbBackend methods
-    @classmethod
-    def get_factory(cls, table_prefix=None):
-        """Svarga model factory. Generates base class which all models
-        should inherit. Please note, that you need one class per application -
-        if you have models split throughout different modules, they all
-        should inherit from one class
-        """
-        base = declarative.declarative_base(metaclass=SvargaDeclarativeMeta,
-                                            metadata=cls._metadata)
-
-        if table_prefix is not None:
-            setattr(base, process.TABLE_PREFIX_ATTRIBUTE, table_prefix)
-
-        return base, columns.SqlaColumns
-
-    @classmethod
-    def get_model_manager(cls):
-        return manager.ModelManager
-
-    @classmethod
-    def add_model(cls, model):
-        cls._models.add(model)
-
-    def sync_db(self):
-        "Create database tables for all registered applications"
-        self._metadata.create_all(self.engine)

svarga/db/sqla/columns.py

-import pickle
-
-import sqlalchemy as sa
-
-
-class SqlaColumnMarker(object):
-    def __init__(self, name, *args, **kwargs):
-        self.name = name
-        self.args = args
-        self.kwargs = kwargs
-
-class SqlaColumns(object):
-    @classmethod
-    def String(cls, max_length=None, **kwargs):
-        return sa.Column(sa.String(max_length), **kwargs)
-
-    @classmethod
-    def Unicode(cls, max_length=None, **kwargs):
-        return sa.Column(sa.Unicode(max_length), **kwargs)
-
-    @classmethod
-    def Text(cls, max_length=None, **kwargs):
-        return sa.Column(sa.Text(max_length), **kwargs)
-
-    @classmethod
-    def UnicodeText(cls, max_length=None, **kwargs):
-        return sa.Column(sa.UnicodeText(max_length), **kwargs)
-
-    @classmethod
-    def Boolean(cls, **kwargs):
-        return sa.Column(sa.Boolean, **kwargs)
-
-    @classmethod
-    def Integer(cls, **kwargs):
-        return sa.Column(sa.Integer, **kwargs)
-
-    @classmethod
-    def Float(cls, **kwargs):
-        return sa.Column(sa.Float, **kwargs)
-
-    @classmethod
-    def Decimal(cls, **kwargs):
-        return sa.Column(sa.DECIMAL, **kwargs)
-
-    @classmethod
-    def DateTime(cls, **kwargs):
-        return sa.Column(sa.DateTime, **kwargs)
-
-    @classmethod
-    def Date(cls, **kwargs):
-        return sa.Column(sa.Date, **kwargs)
-
-    @classmethod
-    def Time(cls, **kwargs):
-        return sa.Column(sa.Time, **kwargs)
-
-    @staticmethod
-    def Serialized(encode=pickle.dumps, decode=pickle.loads, **kwargs):
-        class Serializer(object):
-            dumps = encode
-            loads = decode
-        return sa.Column(sa.PickleType(pickler=Serializer()),
-                         **kwargs)
-
-    @classmethod
-    def ForeignKey(cls, model, **kwargs):
-        marker = SqlaColumnMarker('ForeignKey', **kwargs)
-        marker.model = model
-        return marker
-
-    @classmethod
-    def OneToOne(cls, model, **kwargs):
-        marker = SqlaColumnMarker('OneToOne', **kwargs)
-        marker.model = model
-        return marker
-
-    @classmethod
-    def ManyToMany(cls, model, **kwargs):
-        marker = SqlaColumnMarker('ManyToMany', **kwargs)
-        marker.model = model
-        return marker

svarga/db/sqla/manager.py

-from sqlalchemy.orm import eagerload_all
-
-from svarga import env
-from svarga.db import models
-
-class QueryProxy(object):
-    """
-    Implements simple SQLAlchemy query(foo) wrapper.
-    So, instead of env.sqla.session.query(foo).all() you can do
-    foo.objects.query.all()
-    """
-    def __init__(self, model):
-        self._model = model
-
-    def __getattr__(self, name):
-        return getattr(env.sqla.session.query(self._model), name)
-
-    def __call__(self, *args, **kwargs):
-        return env.sqla.session.query(*args, **kwargs)
-
-class ModelManager(models.BaseModelManager):
-    'Default model manager implementation'
-    def __init__(self, model):
-        super(ModelManager, self).__init__(model)
-        self.query = QueryProxy(model)
-
-    def add(self, model, flush=False):
-        """Register model with session. If model is not added, it won't
-        be saved in the database
-        """
-        if not isinstance(model, self._model):
-            # TODO: Use proper exception type
-            raise Exception('Attempted to add improper type')
-
-        env.sqla.session.add(model)
-
-        if flush:
-            env.sqla.session.flush()
-
-    def delete(self, model):
-        '''Delete model instance'''
-        if not isinstance(model, self._model):
-            raise TypeError('Expected %s type' % self._model)
-
-        env.sqla.session.delete(model)
-
-    def all(self):
-        return self.query.all()
-
-    def first(self):
-        return self.query.first()
-
-    def one(self):
-        return self.query.one()
-
-    def get(self, *args, **kwargs):
-        return self.query.get(*args, **kwargs)
-
-    def filter(self, arg):
-        return self.query.filter(arg)
-
-    def filter_by(self, **kwargs):
-        return self.query.filter_by(**kwargs)
-
-    def order_by(self, arg):
-        return self.query.order_by(arg)
-
-    def fetch(self, limit, offset=None):
-        q = self.query.limit(limit)
-
-        if offset:
-            q = q.offset(offset)
-
-        return q
-
-    def count(self):
-        return self.query.count()
-
-    def flush(self):
-        env.sqla.session.flush()
-
-    def eager(self, *args):
-        return self.query.options(eagerload_all(*args))
-
-    def join(self, *props, **kwargs):
-        return self.query.join(*props, **kwargs)

svarga/db/sqla/meta.py

-from sqlalchemy.orm import ColumnProperty
-from sqlalchemy.orm.interfaces import MANYTOMANY, MANYTOONE, ONETOMANY
-try:
-    from sqlalchemy.orm import RelationshipProperty
-except ImportError: # support for SQLAlchemy < 0.6
-    from sqlalchemy.orm import RelationProperty as RelationshipProperty
-
-from svarga.db.metabase import ModelMetaData
-
-from svarga.utils.ordereddict import OrderedDict
-
-def updater(meta):
-    meta.columns = OrderedDict()
-
-    # Base classes don't have mappers
-    if not hasattr(meta.cls, '__mapper__'):
-        return
-
-    # Process columns and collect relations
-    for v in meta.cls.__mapper__.iterate_properties:
-        n = v.key
-
-        if isinstance(v, ColumnProperty):
-            # Grab column
-            c = v.columns[len(v.columns) - 1]
-
-            # Check if foreign key
-            foreign_key = False
-            if c.foreign_keys:
-                foreign_key = True
-
-            # Check if max_length is present
-            max_length = None
-            if hasattr(c.type, 'length'):
-                max_length = c.type.length
-
-            meta.columns[n] = ModelMetaData(c.type.__class__.__name__,
-                                            c,
-                                            n,
-                                            primary_key = c.primary_key,
-                                            foreign_key = foreign_key,
-                                            max_length = max_length,
-                                            nullable = c.nullable,
-                                            default = c.default and c.default.arg,
-                                            unique = c.unique)
-
-        elif isinstance(v, RelationshipProperty):
-            # Create entry
-            if v.direction is MANYTOONE:
-                name = 'ForeignKey'
-
-                # Special check for backref's uselist
-                if v.backref is not None and not isinstance(v.backref, str):
-                    # SQLA 0.5 check
-                    if hasattr(v, 'kwargs') and v.backref.kwargs.get('uselist'):
-                            name = 'OneToOne'
-                    # SQLA 0.6 check
-                    elif (isinstance(v.backref, tuple)
-                          and v.backref[1].get('uselist')):
-                            name = 'OneToOne'
-            elif v.direction is ONETOMANY:
-                name = 'OneToMany'
-
-                # If not using list, then it is OneToOne
-                if not v.uselist:
-                    name = 'OneToOne'
-            elif v.direction is MANYTOMANY:
-                name = 'ManyToMany'
-            else:
-                raise 'Unsupported relation direction type %s' % (
-                    v.direction.name)
-
-            #if v.uselist == False:
-            #    name = 'OneToOne'
-            if v.secondary is not None and v.uselist:
-                name = 'ManyToMany'
-
-            # TODO: Check if argument can be callable
-            r = v.argument
-
-            if not isinstance(r, basestring):
-                # Hack to get relation target
-                if not hasattr(r, '__name__'):
-                    r = r.__class__
-
-                target = '%s.%s' % (r.__module__, r.__name__)
-            else:
-                target = r
-
-            if not '.' in target:
-                target = '%s.%s' % (r.__module__, target)
-
-            meta.columns[n] = ModelMetaData(name,
-                                            v,
-                                            n,
-                                            nullable = False,
-                                            relation = True,
-                                            target = target)
-

svarga/db/sqla/process.py

-from sqlalchemy.orm import relation, reconstructor, backref
-from sqlalchemy.orm import exc as orm_exc
-import sqlalchemy as sa
-
-from svarga.utils.imports import import_attribute
-from svarga.db.sqla.columns import SqlaColumnMarker
-
-TABLE_PREFIX_ATTRIBUTE = '_svarga_table_prefix'
-
-def _resolve_attribute(module, name):
-    if not '.' in name:
-        name = '%s.%s' % (module, name)
-
-    try:
-        attr = import_attribute(name)
-    except (ImportError, AttributeError):
-        # TODO: Use proper exception type
-        raise Exception('Failed to resolve %s attribute' % name)
-
-    return attr
-
-def postprocess_foreignkey(cls, primary_key, name, column):
-    # Lookup target primary key
-    target = column.model
-    target_key = None
-
-    if isinstance(target, basestring):
-        target = _resolve_attribute(cls.__module__, target)
-
-    for n in target.__table__.columns:
-        if n.primary_key:
-            target_key = n
-            break
-
-    if target_key is None:
-        raise Exception('Target model ''%s'' does not have '
-                        'primary key.' %
-                        target.__name__)
-
-    # Create related column
-    setattr(cls, '%s_id' % name,
-            sa.Column(sa.Integer, sa.ForeignKey(target_key), index=True))
-
-    # Create relation
-    primaryjoin = getattr(cls, '%s_id' % name) == target_key
-    setattr(cls, name, relation(target, primaryjoin=primaryjoin, **column.kwargs))
-
-def postprocess_onetoone(cls, primary_key, name, column):
-    # Lookup target primary key
-    target = column.model
-    target_key = None
-
-    if isinstance(target, basestring):
-        target = _resolve_attribute(cls.__module__, target)
-
-    for n in target.__table__.columns:
-        if n.primary_key:
-            target_key = n
-            break
-
-    if target_key is None:
-        raise Exception('Target model ''%s'' does not have '
-                        'primary key.' %
-                        target.__name__)
-
-    # Fix backref, if required
-    br = column.kwargs.pop('backref', None)
-    if br is not None:
-        if isinstance(br, basestring):
-            br = backref(br, uselist=False)
-        else:
-            br.kwargs['uselist'] = False
-
-    # Create related column
-    setattr(cls, '%s_id' % name,
-            sa.Column(sa.Integer, sa.ForeignKey(target_key), index=True))
-
-    # Create relation
-    setattr(cls, name,
-            relation(target, backref=br, **column.kwargs))
-
-def postprocess_manytomany(cls, primary_key, name, column):
-    target = column.model
-    target_key = None
-
-    if isinstance(target, basestring):
-        target = _resolve_attribute(cls.__module__, target)
-
-    # Lookup target table primary key
-
-    for n in target.__table__.columns:
-        if n.primary_key:
-            target_key = n.name
-            break
-
-    if target_key is None:
-        raise Exception('Target model ''%s'' does not have '
-                        'primary key.' %
-                        target.__name__)
-
-    # Create intermediate table
-    table = sa.Table('%s_%s' % (cls.__tablename__, target.__tablename__),
-                     cls.metadata,
-                     sa.Column('%s_id' % (cls.__tablename__),
-                               sa.Integer,
-                               sa.ForeignKey('%s.%s' % (cls.__tablename__,
-                                                        primary_key)),
-                               index=True),
-                     sa.Column('%s_id' % (target.__tablename__),
-                               sa.Integer,
-                               sa.ForeignKey('%s.%s' % (target.__tablename__,
-                                                        target_key)),
-                               index=True)
-                     )
-
-    # Contribute helper functions
-    def contribute(table):
-        def get_related_id(target):
-            return getattr(table.c, "%s_id" % target.__tablename__)
-
-        table.get_related_id = get_related_id
-
-    contribute(table)
-
-    # Create relation
-    setattr(cls, name, relation(target, secondary=table, **column.kwargs))
-
-# Main preprocessor
-_POSTPROCESSORS = {
-    'ForeignKey': postprocess_foreignkey,
-    'OneToOne': postprocess_onetoone,
-    'ManyToMany': postprocess_manytomany,
-    }
-
-class ModelContribute(object):
-    # Common exceptions
-    IntegrityError = sa.exc.IntegrityError
-    NotFound = orm_exc.NoResultFound
-    MultipleResults = orm_exc.MultipleResultsFound
-
-    def save(self, flush=False):
-        self.objects.add(self, flush=flush)
-
-    def delete(self):
-        self.objects.delete(self)
-
-def _feed_attr(cls, fields, name, attr):
-    fields[name] = attr
-    setattr(cls, name, attr)
-
-def _find_pk(enum):
-    for name, attr in enum:
-        if isinstance(attr, sa.Column) and attr.primary_key:
-            return name
-
-def preprocess(cls, bases, fields):
-    # Quick check if it is model and not model base class. According to
-    # SQLAlchemy docs, only base class will have 'metadata' attribute.
-    if not 'metadata' in cls.__dict__:
-        # Add primary key, if not present
-        primary_key = _find_pk((name, getattr(cls, name))
-                               for name in dir(cls))
-
-        if primary_key is None:
-            # Check if primary key was in one of the super classes
-            for sc in bases:
-                if hasattr(sc, '__table__'):
-                    primary_key = _find_pk((j.name, j)
-                                           for j in sc.__table__.columns)
-
-                    if primary_key is not None:
-                        break
-
-            if primary_key is None:
-                if 'id' in fields:
-                    raise NameError('%r does not have primary key, '
-                                    'but has id attribute' % cls)
-
-                id = sa.Column(sa.Integer, primary_key=True)
-
-                # TODO: Find more appropriate fix for making 'id' first column
-                # Two options, actually: this hack or making our own declarative
-                # wrapper
-                id._creation_order = 0
-
-                _feed_attr(cls, fields, 'id', id)
-
-                primary_key = 'id'
-
-        # If table name is not provided, generate it
-        table_name = getattr(cls, '__tablename__', None)
-        if table_name is None:
-            # Check if there's table already associated
-            if not hasattr(cls, '__table__'):
-                table_prefix = getattr(cls, TABLE_PREFIX_ATTRIBUTE, None)
-                if table_prefix is None:
-                    table_name = cls.__name__.lower()
-                else:
-                    table_name = ('%s_%s' % (table_prefix, cls.__name__)).lower()
-                cls.__tablename__ = table_name
-
-        # Check for __modelinit__
-        if fields.get('__modelinit__'):
-            reconstructor(fields['__modelinit__'])
-
-        # Contribute various helpful methods to model
-        bases += (ModelContribute,)
-
-    return bases
-
-def postprocess(model):
-    # ModelBase won't have table
-    if not hasattr(model, '__table__'):
-        return
-
-    # Find primary key
-    primary_key = _find_pk((j.name, j)
-                           for j in model.__table__.columns)
-
-    if primary_key is None:
-        raise Exception('Missing primary key for '
-                        'model %s.%s' % (model.__module__,
-                                         model.__name__))
-
-    # Post-process columns. Make sure to grab a copy, as __dict__ may change
-    # TODO: Python 3 incompatible
-    for name in model.__dict__.keys():
-        attr = getattr(model, name)
-
-        if isinstance(attr, SqlaColumnMarker):
-            marker_name = attr.name
-
-            # Preprocess should be always present
-            _POSTPROCESSORS[marker_name](model, primary_key, name, attr)