From: Mike Bayer Date: Mon, 5 Oct 2009 22:11:06 +0000 (+0000) Subject: - the mechanics of "backref" have been fully merged into the X-Git-Tag: rel_0_6beta1~264 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bf207ae1b678f2aa45a2bfa6b8cb3f97c3e4af56;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - the mechanics of "backref" have been fully merged into the finer grained "back_populates" system, and take place entirely within the _generate_backref() method of RelationProperty. This makes the initialization procedure of RelationProperty simpler and allows easier propagation of settings (such as from subclasses of RelationProperty) into the reverse reference. The internal BackRef() is gone and backref() returns a plain tuple that is understood by RelationProperty. --- diff --git a/CHANGES b/CHANGES index 12f643e87e..1c2b913c4d 100644 --- a/CHANGES +++ b/CHANGES @@ -65,6 +65,15 @@ CHANGES i.e. ForeignKey(MyRelatedClass.id) doesn't break the "use_get" condition from taking place [ticket:1492] + - the mechanics of "backref" have been fully merged into the + finer grained "back_populates" system, and take place entirely + within the _generate_backref() method of RelationProperty. This + makes the initialization procedure of RelationProperty + simpler and allows easier propagation of settings (such as from + subclasses of RelationProperty) into the reverse reference. + The internal BackRef() is gone and backref() returns a plain + tuple that is understood by RelationProperty. + - sql - the autoincrement flag on column now indicates the column which should be linked to cursor.lastrowid, if that method diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 3d8596e051..5d712df0f8 100644 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -630,10 +630,11 @@ def _deferred_relation(cls, prop): if isinstance(v, basestring): setattr(prop, attr, resolve_arg(v)) - if prop.backref: + if prop.backref and isinstance(prop.backref, tuple): + key, kwargs = prop.backref for attr in ('primaryjoin', 'secondaryjoin', 'secondary', 'foreign_keys', 'remote_side', 'order_by'): - if attr in prop.backref.kwargs and isinstance(prop.backref.kwargs[attr], basestring): - prop.backref.kwargs[attr] = resolve_arg(prop.backref.kwargs[attr]) + if attr in kwargs and isinstance(kwargs[attr], basestring): + kwargs[attr] = resolve_arg(kwargs[attr]) return prop diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 713ade3e5a..a343dffb81 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -38,7 +38,6 @@ from sqlalchemy.orm.util import ( with_parent, ) from sqlalchemy.orm.properties import ( - BackRef, ColumnProperty, ComparableProperty, CompositeProperty, @@ -582,14 +581,14 @@ def composite(class_, *cols, **kwargs): def backref(name, **kwargs): - """Create a BackRef object with explicit arguments, which are the same + """Create a back reference with explicit arguments, which are the same arguments one can send to ``relation()``. Used with the `backref` keyword argument to ``relation()`` in place of a string argument. """ - return BackRef(name, **kwargs) + return (name, kwargs) def deferred(*columns, **kwargs): """Return a ``DeferredColumnProperty``, which indicates this diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 445496e34a..f49f5fe88a 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -417,16 +417,6 @@ class RelationProperty(StrategizedProperty): if backref: raise sa_exc.ArgumentError("backref and back_populates keyword arguments are mutually exclusive") self.backref = None - elif isinstance(backref, basestring): - # propagate explicitly sent primary/secondary join conditions to the BackRef object if - # just a string was sent - if secondary is not None: - # reverse primary/secondary in case of a many-to-many - self.backref = BackRef(backref, primaryjoin=secondaryjoin, - secondaryjoin=primaryjoin, passive_updates=self.passive_updates) - else: - self.backref = BackRef(backref, primaryjoin=primaryjoin, - secondaryjoin=secondaryjoin, passive_updates=self.passive_updates) else: self.backref = backref @@ -705,16 +695,18 @@ class RelationProperty(StrategizedProperty): if self.direction in (ONETOMANY, MANYTOONE) and self.direction == other.direction: raise sa_exc.ArgumentError("%s and back-reference %s are both of the same direction %r." - " Did you mean to set remote_side on the many-to-one side ?" % (self, other, self.direction)) + " Did you mean to set remote_side on the many-to-one side ?" % (other, self, self.direction)) def do_init(self): self._get_target() + self._assert_is_primary() self._process_dependent_arguments() self._determine_joins() self._determine_synchronize_pairs() self._determine_direction() self._determine_local_remote_pairs() self._post_init() + self._generate_backref() super(RelationProperty, self).do_init() def _get_target(self): @@ -886,6 +878,7 @@ class RelationProperty(StrategizedProperty): def _determine_direction(self): if self.secondaryjoin is not None: self.direction = MANYTOMANY + elif self._refers_to_parent_table(): # self referential defaults to ONETOMANY unless the "remote" side is present # and does not reference any foreign key columns @@ -997,7 +990,66 @@ class RelationProperty(StrategizedProperty): self.local_side, self.remote_side = [util.ordered_column_set(x) for x in zip(*list(self.local_remote_pairs))] + def _assert_is_primary(self): + if not self.is_primary() and \ + not mapper.class_mapper(self.parent.class_, compile=False)._get_property(self.key, raiseerr=False): + + raise sa_exc.ArgumentError("Attempting to assign a new relation '%s' to " + "a non-primary mapper on class '%s'. New relations can only be " + "added to the primary mapper, i.e. the very first " + "mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__)) + + def _generate_backref(self): + if not self.is_primary(): + return + if self.backref is not None and not self.back_populates: + if isinstance(self.backref, basestring): + backref_key, kwargs = self.backref, {} + else: + backref_key, kwargs = self.backref + + mapper = self.mapper.primary_mapper() + if mapper._get_property(backref_key, raiseerr=False) is not None: + raise sa_exc.ArgumentError("Error creating backref '%s' on relation '%s': " + "property of that name exists on mapper '%s'" % (backref_key, self, mapper)) + + if self.secondary is not None: + pj = kwargs.pop('primaryjoin', self.secondaryjoin) + sj = kwargs.pop('secondaryjoin', self.primaryjoin) + else: + pj = kwargs.pop('primaryjoin', self.primaryjoin) + sj = kwargs.pop('secondaryjoin', None) + if sj: + raise sa_exc.InvalidRequestError( + "Can't assign 'secondaryjoin' on a backref against " + "a non-secondary relation.") + + foreign_keys = kwargs.pop('foreign_keys', self._foreign_keys) + + parent = self.parent.primary_mapper() + kwargs.setdefault('viewonly', self.viewonly) + kwargs.setdefault('post_update', self.post_update) + + self.back_populates = backref_key + relation = RelationProperty( + parent, + self.secondary, + pj, + sj, + foreign_keys=foreign_keys, + back_populates=self.key, + **kwargs) + + mapper._configure_property(backref_key, relation) + + + if self.back_populates: + self.extension = util.to_list(self.extension) or [] + self.extension.append(attributes.GenericBackrefExtension(self.back_populates)) + self._add_reverse_property(self.back_populates) + + def _post_init(self): if self._should_log_info: self.logger.info("%s setup primary join %s", self, self.primaryjoin) @@ -1006,32 +1058,13 @@ class RelationProperty(StrategizedProperty): self.logger.info("%s secondary synchronize pairs [%s]", self, ",".join(("(%s => %s)" % (l, r) for l, r in self.secondary_synchronize_pairs or []))) self.logger.info("%s local/remote pairs [%s]", self, ",".join("(%s / %s)" % (l, r) for l, r in self.local_remote_pairs)) self.logger.info("%s relation direction %s", self, self.direction) - - if self.uselist is None and self.direction is MANYTOONE: - self.uselist = False - + if self.uselist is None: - self.uselist = True - + self.uselist = self.direction is not MANYTOONE + if not self.viewonly: self._dependency_processor = dependency.create_dependency_processor(self) - # primary property handler, set up class attributes - if self.is_primary(): - if self.back_populates: - self.extension = util.to_list(self.extension) or [] - self.extension.append(attributes.GenericBackrefExtension(self.back_populates)) - self._add_reverse_property(self.back_populates) - - if self.backref is not None: - self.backref.compile(self) - elif not mapper.class_mapper(self.parent.class_, compile=False)._get_property(self.key, raiseerr=False): - raise sa_exc.ArgumentError("Attempting to assign a new relation '%s' to " - "a non-primary mapper on class '%s'. New relations can only be " - "added to the primary mapper, i.e. the very first " - "mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__)) - - def _refers_to_parent_table(self): for c, f in self.synchronize_pairs: if c.table is f.table: @@ -1144,59 +1177,6 @@ class RelationProperty(StrategizedProperty): PropertyLoader = RelationProperty log.class_logger(RelationProperty) -class BackRef(object): - """Attached to a RelationProperty to indicate a complementary reverse relationship. - - Handles the job of creating the opposite RelationProperty according to configuration. - - Alternatively, two explicit RelationProperty objects can be associated bidirectionally - using the back_populates keyword argument on each. - - """ - - def __init__(self, key, _prop=None, **kwargs): - self.key = key - self.kwargs = kwargs - self.prop = _prop - self.extension = attributes.GenericBackrefExtension(self.key) - - def compile(self, prop): - if self.prop: - return - - self.prop = prop - - mapper = prop.mapper.primary_mapper() - if mapper._get_property(self.key, raiseerr=False) is None: - if prop.secondary is not None: - pj = self.kwargs.pop('primaryjoin', prop.secondaryjoin) - sj = self.kwargs.pop('secondaryjoin', prop.primaryjoin) - else: - pj = self.kwargs.pop('primaryjoin', prop.primaryjoin) - sj = self.kwargs.pop('secondaryjoin', None) - if sj: - raise sa_exc.InvalidRequestError( - "Can't assign 'secondaryjoin' on a backref against " - "a non-secondary relation.") - - foreign_keys = self.kwargs.pop('foreign_keys', prop._foreign_keys) - - parent = prop.parent.primary_mapper() - self.kwargs.setdefault('viewonly', prop.viewonly) - self.kwargs.setdefault('post_update', prop.post_update) - - relation = RelationProperty(parent, prop.secondary, pj, sj, foreign_keys=foreign_keys, - backref=BackRef(prop.key, _prop=prop), - **self.kwargs) - - mapper._configure_property(self.key, relation); - - prop._add_reverse_property(self.key) - - else: - raise sa_exc.ArgumentError("Error creating backref '%s' on relation '%s': " - "property of that name exists on mapper '%s'" % (self.key, prop, mapper)) - mapper.ColumnProperty = ColumnProperty mapper.SynonymProperty = SynonymProperty mapper.ComparableProperty = ComparableProperty diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 5192417439..a1369fa6b3 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -35,13 +35,10 @@ def _register_attribute(strategy, mapper, useobject, attribute_ext = util.to_list(prop.extension) or [] if useobject and prop.single_parent: - attribute_ext.append(_SingleParentValidator(prop)) + attribute_ext.insert(0, _SingleParentValidator(prop)) - if getattr(prop, 'backref', None): - attribute_ext.append(prop.backref.extension) - if prop.key in prop.parent._validators: - attribute_ext.append(mapperutil.Validator(prop.key, prop.parent._validators[prop.key])) + attribute_ext.insert(0, mapperutil.Validator(prop.key, prop.parent._validators[prop.key])) if useobject: attribute_ext.append(sessionlib.UOWEventHandler(prop.key))