from sqlalchemy import create_session,relation,mapper,\
join, DynamicMetaData, class_mapper, \
util, Integer
from sqlalchemy import and_,or_
from sqlalchemy import Table,Column,ForeignKey
from sqlalchemy.ext.sessioncontext import SessionContext
from sqlalchemy.ext.assignmapper import assign_mapper
from sqlalchemy import backref
import sqlalchemy
import inspect
import sys
#
# the "proxy" to the database engine... this can be swapped out at runtime
#
metadata = DynamicMetaData("activemapper")
try:
objectstore = sqlalchemy.objectstore
except AttributeError:
# thread local SessionContext
class Objectstore(object):
def __init__(self, *args, **kwargs):
self.context = SessionContext(*args, **kwargs)
def __getattr__(self, name):
return getattr(self.context.current, name)
session = property(lambda s:s.context.current)
objectstore = Objectstore(create_session)
#
# declarative column declaration - this is so that we can infer the colname
#
class column(object):
def __init__(self, coltype, colname=None, foreign_key=None,
primary_key=False, *args, **kwargs):
if isinstance(foreign_key, basestring):
foreign_key = ForeignKey(foreign_key)
self.coltype = coltype
self.colname = colname
self.foreign_key = foreign_key
self.primary_key = primary_key
self.kwargs = kwargs
self.args = args
#
# declarative relationship declaration
#
class relationship(object):
def __init__(self, classname, colname=None, backref=None, private=False,
lazy=True, uselist=True, secondary=None, order_by=False):
self.classname = classname
self.colname = colname
self.backref = backref
self.private = private
self.lazy = lazy
self.uselist = uselist
self.secondary = secondary
self.order_by = order_by
def process(self, klass, propname, relations):
relclass = ActiveMapperMeta.classes[self.classname]
if isinstance(self.order_by, str):
self.order_by = [ self.order_by ]
if isinstance(self.order_by, list):
for itemno in range(len(self.order_by)):
if isinstance(self.order_by[itemno], str):
self.order_by[itemno] = \
getattr(relclass.c, self.order_by[itemno])
backref = self.create_backref(klass)
relations[propname] = relation(relclass.mapper,
secondary=self.secondary,
backref=backref,
private=self.private,
lazy=self.lazy,
uselist=self.uselist,
order_by=self.order_by)
def create_backref(self, klass):
if self.backref is None:
return None
relclass = ActiveMapperMeta.classes[self.classname]
if klass.__name__ == self.classname:
br_fkey = getattr(relclass.c, self.colname)
else:
br_fkey = None
return create_backref(self.backref, foreignkey=br_fkey)
class one_to_many(relationship):
def __init__(self, classname, colname=None, backref=None, private=False,
lazy=True, order_by=False):
relationship.__init__(self, classname, colname, backref, private,
lazy, uselist=True, order_by=order_by)
class one_to_one(relationship):
def __init__(self, classname, colname=None, backref=None, private=False,
lazy=True, order_by=False):
relationship.__init__(self, classname, colname, backref, private,
lazy, uselist=False, order_by=order_by)
def create_backref(self, klass):
if self.backref is None:
return None
relclass = ActiveMapperMeta.classes[self.classname]
if klass.__name__ == self.classname:
br_fkey = getattr(relclass.c, self.colname)
else:
br_fkey = None
return create_backref(self.backref, foreignkey=br_fkey, uselist=False)
class many_to_many(relationship):
def __init__(self, classname, secondary, backref=None, lazy=True,
order_by=False):
relationship.__init__(self, classname, None, backref, False, lazy,
uselist=True, secondary=secondary,
order_by=order_by)
#
# SQLAlchemy metaclass and superclass that can be used to do SQLAlchemy
# mapping in a declarative way, along with a function to process the
# relationships between dependent objects as they come in, without blowing
# up if the classes aren't specified in a proper order
#
__deferred_classes__ = {}
__processed_classes__ = {}
def process_relationships(klass, was_deferred=False):
# first, we loop through all of the relationships defined on the
# class, and make sure that the related class already has been
# completely processed and defer processing if it has not
defer = False
for propname, reldesc in klass.relations.items():
found = (reldesc.classname == klass.__name__ or reldesc.classname in __processed_classes__)
if not found:
defer = True
break
# next, we loop through all the columns looking for foreign keys
# and make sure that we can find the related tables (they do not
# have to be processed yet, just defined), and we defer if we are
# not able to find any of the related tables
if not defer:
for col in klass.columns:
if col.foreign_key is not None:
found = False
table_name = col.foreign_key._colspec.rsplit('.', 1)[0]
for other_klass in ActiveMapperMeta.classes.values():
if other_klass.table.fullname.lower() == table_name.lower():
found = True
if not found:
defer = True
break
if defer and not was_deferred:
__deferred_classes__[klass.__name__] = klass
# if we are able to find all related and referred to tables, then
# we can go ahead and assign the relationships to the class
if not defer:
relations = {}
for propname, reldesc in klass.relations.items():
reldesc.process(klass, propname, relations)
class_mapper(klass).add_properties(relations)
if klass.__name__ in __deferred_classes__:
del __deferred_classes__[klass.__name__]
__processed_classes__[klass.__name__] = klass
# finally, loop through the deferred classes and attempt to process
# relationships for them
if not was_deferred:
# loop through the list of deferred classes, processing the
# relationships, until we can make no more progress
last_count = len(__deferred_classes__) + 1
while last_count > len(__deferred_classes__):
last_count = len(__deferred_classes__)
deferred = __deferred_classes__.copy()
for deferred_class in deferred.values():
process_relationships(deferred_class, was_deferred=True)
class ActiveMapperMeta(type):
classes = {}
metadatas = util.Set()
def __init__(cls, clsname, bases, dict):
table_name = clsname.lower()
columns = []
relations = {}
autoload = False
_metadata = getattr(sys.modules[cls.__module__],
"__metadata__", metadata)
version_id_col = None
version_id_col_object = None
if 'mapping' in dict:
found_pk = False
members = inspect.getmembers(dict.get('mapping'))
for name, value in members:
if name == '__table__':
table_name = value
continue
if '__metadata__' == name:
_metadata= value
continue
if '__autoload__' == name:
autoload = True
continue
if '__version_id_col__' == name:
version_id_col = value
if name.startswith('__'): continue
if isinstance(value, column):
if value.primary_key == True: found_pk = True
if value.foreign_key:
col = Column(value.colname or name,
value.coltype,
value.foreign_key,
primary_key=value.primary_key,
*value.args, **value.kwargs)
else:
col = Column(value.colname or name,
value.coltype,
primary_key=value.primary_key,
*value.args, **value.kwargs)
columns.append(col)
continue
if isinstance(value, relationship):
relations[name] = value
if not found_pk and not autoload:
col = Column('id', Integer, primary_key=True)
cls.mapping.id = col
columns.append(col)
assert _metadata is not None, "No MetaData specified"
ActiveMapperMeta.metadatas.add(_metadata)
if not autoload:
cls.table = Table(table_name, _metadata, *columns)
cls.columns = columns
else:
cls.table = Table(table_name, _metadata, autoload=True)
cls.columns = cls.table._columns
# check for inheritence
if version_id_col is not None:
version_id_col_object = getattr(cls.table.c, version_id_col, None)
assert(version_id_col_object is not None, "version_id_col (%s) does not exist." % version_id_col)
if hasattr(bases[0], "mapping"):
cls._base_mapper= bases[0].mapper
assign_mapper(objectstore.context, cls, cls.table,
inherits=cls._base_mapper, version_id_col=version_id_col_object)
else:
assign_mapper(objectstore.context, cls, cls.table, version_id_col=version_id_col_object)
cls.relations = relations
ActiveMapperMeta.classes[clsname] = cls
process_relationships(cls)
super(ActiveMapperMeta, cls).__init__(clsname, bases, dict)
class ActiveMapper(object):
__metaclass__ = ActiveMapperMeta
def set(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
#
# a utility function to create all tables for all ActiveMapper classes
#
def create_tables():
for metadata in ActiveMapperMeta.metadatas:
metadata.create_all()
def drop_tables():
for metadata in ActiveMapperMeta.metadatas:
metadata.drop_all()
|