diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2009-10-05 22:11:06 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2009-10-05 22:11:06 +0000 |
| commit | bf207ae1b678f2aa45a2bfa6b8cb3f97c3e4af56 (patch) | |
| tree | 45a3b2a92cc15a117ce88491f48e3f6e76540ce2 /lib | |
| parent | 9f2e94fc2c5f262c9339f1040b9bb208a45dc401 (diff) | |
| download | sqlalchemy-bf207ae1b678f2aa45a2bfa6b8cb3f97c3e4af56.tar.gz | |
- 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.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/ext/declarative.py | 7 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 152 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 7 |
4 files changed, 74 insertions, 97 deletions
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 3d8596e05..5d712df0f 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 713ade3e5..a343dffb8 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 445496e34..f49f5fe88 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 519241743..a1369fa6b 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)) |
