else:
return EagerLoader(mapper, secondary, primaryjoin, secondaryjoin, **options)
-def relation_mapper(class_, selectable, secondary = None, primaryjoin = None, secondaryjoin = None, table = None, properties = None, lazy = True, uselist = True, **options):
- return relation_loader(mapper(class_, selectable, table = table, properties = properties, **options), secondary, primaryjoin, secondaryjoin, lazy = lazy, uselist = uselist, **options)
+def relation_mapper(class_, selectable, secondary = None, primaryjoin = None, secondaryjoin = None, table = None, properties = None, lazy = True, uselist = True, direction = None, **options):
+ return relation_loader(mapper(class_, selectable, table = table, properties = properties, **options), secondary, primaryjoin, secondaryjoin, lazy = lazy, uselist = uselist, direction = direction, **options)
_mappers = {}
def mapper(*args, **params):
def lazyload(name):
return EagerLazySwitcher(name, toeager = False)
+def object_mapper(object):
+ try:
+ return _mappers[object._mapper]
+ except AttributeError:
+ try:
+ return _mappers[object.__class__._mapper]
+ except AttributeError:
+ raise "Object " + object.__class__.__name__ + "/" + repr(id(object)) + " has no mapper specified"
+
class Mapper(object):
def __init__(self, class_, selectable, table = None, scope = "thread", properties = None, echo = None, **kwargs):
self.class_ = class_
self.init()
def hash_key(self):
- return mapper_hash_key(
- self.class_,
- self.selectable,
- self.table,
- self.properties,
- self.scope,
- self.echo
- )
+ if not hasattr(self, 'hashkey'):
+ self.hashkey = mapper_hash_key(
+ self.class_,
+ self.selectable,
+ self.table,
+ self.properties,
+ self.scope,
+ self.echo
+ )
+ return self.hashkey
def set_property(self, key, prop):
self.props[key] = prop
def init(self):
[prop.init(key, self) for key, prop in self.props.iteritems()]
+ self.class_._mapper = self.hash_key()
def instances(self, cursor):
result = util.HistoryArraySet()
for prop in self.props.values():
prop.save(obj, traverse)
+ def register_dependencies(self, obj, uow):
+ for prop in self.props.values():
+ prop.register_dependencies(obj, uow)
+
def transaction(self, f):
return self.table.engine.multi_transaction(self.tables, f)
exists = objectstore.has_key(identitykey)
if not exists:
instance = self.class_()
+ instance._mapper = self.hash_key()
for column in self.selectable.primary_keys:
if row[column.label] is None:
return None
def delete(self, object):
"""called when the instance is being deleted"""
pass
+
+ def register_dependencies(self, obj, uow):
+ pass
class ColumnProperty(MapperProperty):
"""describes an object attribute that corresponds to a table column."""
class PropertyLoader(MapperProperty):
"""describes an object property that holds a list of items that correspond to a related
database table."""
- def __init__(self, mapper, secondary, primaryjoin, secondaryjoin, uselist = True):
+ def __init__(self, mapper, secondary, primaryjoin, secondaryjoin, uselist = True, direction = None):
self.uselist = uselist
self.mapper = mapper
self.target = self.mapper.selectable
self.secondary = secondary
self.primaryjoin = primaryjoin
self.secondaryjoin = secondaryjoin
+ self.direction = direction
self._hash_key = "%s(%s, %s, %s, %s, uselist=%s)" % (self.__class__.__name__, hash_key(mapper), hash_key(secondary), hash_key(primaryjoin), hash_key(secondaryjoin), repr(self.uselist))
-
+ if self.direction is not None and self.direction != 'left' and self.direction != 'right':
+ raise "direction propery must be 'left', 'right' or None"
+
def hash_key(self):
return self._hash_key
def init(self, key, parent):
self.key = key
self.parent = parent
+
+ # if join conditions were not specified, figure them out based on primary keys
if self.secondary is not None:
if self.secondaryjoin is None:
self.secondaryjoin = self.match_primaries(self.target, self.secondary)
else:
if self.primaryjoin is None:
self.primaryjoin = self.match_primaries(parent.selectable, self.target)
+
+ # if the foreign key wasnt specified and theres no assocaition table, try to figure
+ # out who is dependent on who. we dont need all the foreign keys represented in the join,
+ # just one of them.
+ if self.foreignkey is None and self.secondaryjoin is None:
+ # else we usually will have a one-to-many where the secondary depends on the primary
+ # but its possible that its reversed
+ w = PropertyLoader.FindDependent()
+ w.accept_visitor(self.primaryjoin)
+ if w.dependent is None:
+ raise "cant determine primary foreign key in the join relationship....specify foreignkey=<column>"
+ else:
+ self.foreignkey = w.dependent
if not hasattr(parent.class_, key):
setattr(parent.class_, key, SmartProperty(key).property(usehistory = True, uselist = self.uselist))
+ class FindDependent(sql.ClauseVisitor):
+ def visit_binary(self, binary):
+ if binary.operator == '=':
+ if binary.left.primary_key:
+ if self.dependent == binary.left:
+ raise "bidirectional dependency not supported...specify foreignkey"
+ self.dependent = binary.right
+ elif binary.right.primary_key:
+ if self.dependent == binary.right:
+ raise "bidirectional dependency not supported...specify foreignkey"
+ self.dependent = binary.left
+
+
def match_primaries(self, primary, secondary):
pk = primary.primary_keys
if len(pk) == 1:
else:
return sql.and_([pk == secondary.c[pk.name] for pk in primary.primary_keys])
+ def register_dependencies(self, obj, uow):
+ if self.uselist:
+ childlist = objectstore.uow().register_list_attribute(obj, self.key)
+ else:
+ childlist = objectstore.uow().register_attribute(obj, self.key)
+
+ if self.secondarytable is not None:
+ # TODO: put a "row" as a dependency into the UOW somehow
+ pass
+ elif self.foreignkey.table == self.target:
+ for child in childlist.added_items():
+ uow.register_dependency(obj, child)
+ elif self.foreignkey.table == self.secondary:
+ for child in childlist.added_items():
+ uow.register_dependency(child, obj)
+
+
def save(self, obj, traverse):
# saves child objects
class UnitOfWork(object):
def __init__(self):
+ self.new = util.HashSet()
self.dirty = util.HashSet()
- self.clean = util.HashSet()
self.deleted = util.HashSet()
self.attribute_history = weakref.WeakKeyDictionary()
return childlist
def register_clean(self, obj):
- self.clean.append(obj)
try:
del self.dirty[obj]
except KeyError:
pass
def register_new(self, obj):
- pass
+ self.new.append(obj)
def register_dirty(self, obj):
self.dirty.append(obj)
- try:
- del self.clean[obj]
- except KeyError:
- pass
def is_dirty(self, obj):
if not self.dirty.contains(obj):
# if we know nothing about this object, register it as dirty (or new ?)
if not self.clean.contains(obj):
- # TODO: should this be register_new ?
- self.register_dirty(obj)
+ self.register_new(obj)
return True
return False
else:
def register_deleted(self, obj):
pass
-
+
def commit(self):
- for item in self.dirty:
- self.clean.append(item)
+ import sqlalchemy.mapper as mapper
+
+ self.dependencies = {}
+
+ for obj in self.new:
+ mapper = mapper.object_mapper(obj)
+ mapper.register_dependencies(item, self)
+ for obj in self.dirty:
+ mapper = mapper.object_mapper(obj)
+ mapper.register_dependencies(obj, self)
+# for item in self.deleted:
+# mapper = mapper.object_mapper(item)
+# sort save instructions
+# execute save instructions
+# hmmmmm, as we save items, we have to populate the dependencies too
+# then the save comes down to them and they are populated
self.dirty.clear()
+# self.deleted.clear()
+
+ def register_dependency(self, obj, dependency):
+ pass
+
uow = util.ScopedRegistry(lambda: UnitOfWork(), "thread")
\ No newline at end of file