diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2005-12-09 05:08:51 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2005-12-09 05:08:51 +0000 |
| commit | 69ad2955bdb33eb45939a01d95bcff240a2d9fb6 (patch) | |
| tree | 8e285e2c1850a94ae3e44a7fe118f86bf8ff3448 /lib/sqlalchemy | |
| parent | ddf671347205aae40e501c5533c2dd7a94a6a3a1 (diff) | |
| download | sqlalchemy-69ad2955bdb33eb45939a01d95bcff240a2d9fb6.tar.gz | |
build in 'backref' property argument
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/attributes.py | 17 | ||||
| -rw-r--r-- | lib/sqlalchemy/mapping/properties.py | 48 | ||||
| -rw-r--r-- | lib/sqlalchemy/util.py | 38 |
3 files changed, 84 insertions, 19 deletions
diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index 72b026387..c42a54be5 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -123,7 +123,7 @@ class ListElement(util.HistoryArraySet): self.obj.__dict__[self.key] = value self.set_data(value) def delattr(self, value): - pass + pass def _setrecord(self, item): res = util.HistoryArraySet._setrecord(self, item) if res: @@ -194,34 +194,33 @@ class AttributeExtension(object): pass def set(self, obj, child, oldchild): pass - + class ListBackrefExtension(AttributeExtension): def __init__(self, key): self.key = key def append(self, obj, child): - getattr(child, self.key).append_nohistory(obj) + getattr(child, self.key).append(obj) def delete(self, obj, child): - getattr(child, self.key).remove_nohistory(obj) - + getattr(child, self.key).remove(obj) class OTMBackrefExtension(AttributeExtension): def __init__(self, key): self.key = key def append(self, obj, child): prop = child.__class__._attribute_manager.get_history(child, self.key) - prop.setattr_clean(obj) + prop.setattr(obj) # prop.setattr(obj) def delete(self, obj, child): prop = child.__class__._attribute_manager.get_history(child, self.key) - prop.delattr_clean() + prop.delattr() class MTOBackrefExtension(AttributeExtension): def __init__(self, key): self.key = key def set(self, obj, child, oldchild): if oldchild is not None: - getattr(oldchild, self.key).remove_nohistory(obj) + getattr(oldchild, self.key).remove(obj) if child is not None: - getattr(child, self.key).append_nohistory(obj) + getattr(child, self.key).append(obj) # getattr(child, self.key).append(obj) class AttributeManager(object): diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py index 4b82157d3..08897f2e6 100644 --- a/lib/sqlalchemy/mapping/properties.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -21,6 +21,7 @@ import sqlalchemy.sql as sql import sqlalchemy.schema as schema import sqlalchemy.engine as engine import sqlalchemy.util as util +import sqlalchemy.attributes as attributes import mapper import objectstore import random @@ -56,13 +57,13 @@ class ColumnProperty(MapperProperty): mapper.ColumnProperty = ColumnProperty class PropertyLoader(MapperProperty): - LEFT = 0 - RIGHT = 1 - CENTER = 2 + LEFT = 0 # one-to-many + RIGHT = 1 # many-to-one + CENTER = 2 # many-to-many """describes an object property that holds a single item or list of items that correspond to a related database table.""" - def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, live=False, isoption=False, association=None, selectalias=None, order_by=None, attributeext=None): + def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, live=False, isoption=False, association=None, selectalias=None, order_by=None, attributeext=None, backref=None, is_backref=False): self.uselist = uselist self.argument = argument self.secondary = secondary @@ -76,6 +77,8 @@ class PropertyLoader(MapperProperty): self.selectalias = selectalias self.order_by=util.to_list(order_by) self.attributeext=attributeext + self.backref = backref + self.is_backref = is_backref self._hash_key = "%s(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.__class__.__name__, hash_key(self.argument), hash_key(secondary), hash_key(primaryjoin), hash_key(secondaryjoin), hash_key(foreignkey), repr(uselist), repr(private), hash_key(self.order_by)) def _copy(self): @@ -126,10 +129,31 @@ class PropertyLoader(MapperProperty): self.uselist = True self._compile_synchronizers() - + + # primary property handler, set up class attributes if self._is_primary(): + # if a backref name is defined, set up an extension to populate + # attributes in the other direction + if self.backref is not None: + if self.direction == PropertyLoader.LEFT: + self.attributeext = attributes.OTMBackrefExtension(self.backref) + elif self.direction == PropertyLoader.RIGHT: + self.attributeext = attributes.MTOBackrefExtension(self.backref) + else: + self.attributeext = attributes.ListBackrefExtension(self.backref) + + # set our class attribute self._set_class_attribute(parent.class_, key) - + + if self.backref is not None: + # try to set a LazyLoader on our mapper referencing the parent mapper + if not self.mapper.props.has_key(self.backref): + self.mapper.add_property(self.backref, LazyLoader(self.parent, self.secondary, self.primaryjoin, self.secondaryjoin, backref=self.key, is_backref=True)); + else: + # else set one of us as the "backreference" + if not self.mapper.props[self.backref].is_backref: + self.is_backref=True + def _is_primary(self): """a return value of True indicates we are the primary PropertyLoader for this loader's attribute on our mapper's class. It means we can set the object's attribute behavior @@ -277,6 +301,11 @@ class PropertyLoader(MapperProperty): # or delete any objects, but just marks a dependency on the two # related mappers. its dependency processor then populates the # association table. + + if self.is_backref: + # if we are the "backref" half of a two-way backref + # relationship, let the other mapper handle inserting the rows + return stub = PropertyLoader.MapperStub() uowcommit.register_dependency(self.parent, stub) uowcommit.register_dependency(self.mapper, stub) @@ -674,10 +703,11 @@ class EagerLazyOption(MapperOption): else: class_ = LazyLoader - for arg in ('primaryjoin', 'secondaryjoin', 'foreignkey', 'uselist', 'private', 'live', 'isoption', 'association', 'selectalias', 'order_by', 'attributeext'): - self.kwargs.setdefault(arg, getattr(oldprop, arg)) + # create a clone of the class using mostly the arguments from the original self.kwargs['isoption'] = True - mapper.set_property(key, class_(submapper, oldprop.secondary, **self.kwargs )) + self.kwargs['argument'] = submapper + kwargs = util.constructor_args(oldprop, **self.kwargs) + mapper.set_property(key, class_(**kwargs )) class Aliasizer(sql.ClauseVisitor): """converts a table instance within an expression to be an alias of that table.""" diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index d280368f6..bc23d6080 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -16,7 +16,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. __all__ = ['OrderedProperties', 'OrderedDict'] -import thread, weakref, UserList,string +import thread, weakref, UserList,string, inspect def to_list(x): if x is None: @@ -345,3 +345,39 @@ class ScopedRegistry(object): def _clear_application(self): self.application = createfunc() + + +def constructor_args(instance, **kwargs): + classobj = instance.__class__ + + argspec = inspect.getargspec(classobj.__init__.im_func) + + argnames = argspec[0] or [] + defaultvalues = argspec[3] or [] + + (requiredargs, namedargs) = ( + argnames[0:len(argnames) - len(defaultvalues)], + argnames[len(argnames) - len(defaultvalues):] + ) + + newparams = {} + + for arg in requiredargs: + if arg == 'self': + continue + elif kwargs.has_key(arg): + newparams[arg] = kwargs[arg] + else: + newparams[arg] = getattr(instance, arg) + + for arg in namedargs: + if kwargs.has_key(arg): + newparams[arg] = kwargs[arg] + else: + if hasattr(instance, arg): + newparams[arg] = getattr(instance, arg) + else: + raise "instance has no attribute '%s'" % arg + + return newparams +
\ No newline at end of file |
