diff options
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 23 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/dynamic.py | 68 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/properties.py | 28 |
3 files changed, 72 insertions, 47 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 99443d00d..3c539b8f4 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -402,9 +402,11 @@ def relation(argument, secondary=None, **kwargs): """ return RelationProperty(argument, secondary=secondary, **kwargs) -def dynamic_loader(argument, secondary=None, primaryjoin=None, secondaryjoin=None, - foreign_keys=None, backref=None, post_update=False, cascade=False, remote_side=None, enable_typechecks=True, - passive_deletes=False, order_by=None, comparator_factory=None): +def dynamic_loader(argument, secondary=None, primaryjoin=None, + secondaryjoin=None, foreign_keys=None, backref=None, + post_update=False, cascade=False, remote_side=None, + enable_typechecks=True, passive_deletes=False, + order_by=None, comparator_factory=None, query_class=None): """Construct a dynamically-loading mapper property. This property is similar to :func:`relation`, except read @@ -430,15 +432,20 @@ def dynamic_loader(argument, secondary=None, primaryjoin=None, secondaryjoin=Non generally mutually exclusive with the use of the *secondary* keyword argument. + :param query_class: + Optional, a custom Query subclass to be used as the basis for + dynamic collection. """ from sqlalchemy.orm.dynamic import DynaLoader - return RelationProperty(argument, secondary=secondary, primaryjoin=primaryjoin, - secondaryjoin=secondaryjoin, foreign_keys=foreign_keys, backref=backref, - post_update=post_update, cascade=cascade, remote_side=remote_side, enable_typechecks=enable_typechecks, - passive_deletes=passive_deletes, order_by=order_by, comparator_factory=comparator_factory, - strategy_class=DynaLoader) + return RelationProperty( + argument, secondary=secondary, primaryjoin=primaryjoin, + secondaryjoin=secondaryjoin, foreign_keys=foreign_keys, backref=backref, + post_update=post_update, cascade=cascade, remote_side=remote_side, + enable_typechecks=enable_typechecks, passive_deletes=passive_deletes, + order_by=order_by, comparator_factory=comparator_factory, + strategy_class=DynaLoader, query_class=query_class) def column_property(*args, **kwargs): """Provide a column-level property for use with a Mapper. diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index eed50cd6d..1bc0994c1 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -24,7 +24,7 @@ from sqlalchemy.orm.util import _state_has_identity, has_identity class DynaLoader(strategies.AbstractRelationLoader): def init_class_attribute(self): self.is_class_level = True - self._register_attribute(self.parent.class_, impl_class=DynamicAttributeImpl, target_mapper=self.parent_property.mapper, order_by=self.parent_property.order_by) + self._register_attribute(self.parent.class_, impl_class=DynamicAttributeImpl, target_mapper=self.parent_property.mapper, order_by=self.parent_property.order_by, query_class=self.parent_property.query_class) def create_row_processor(self, selectcontext, path, mapper, row, adapter): return (None, None) @@ -34,12 +34,17 @@ log.class_logger(DynaLoader) class DynamicAttributeImpl(attributes.AttributeImpl): uses_objects = True accepts_scalar_loader = False - - def __init__(self, class_, key, typecallable, class_manager, target_mapper, order_by, **kwargs): - super(DynamicAttributeImpl, self).__init__(class_, key, typecallable, class_manager, **kwargs) + + def __init__(self, class_, key, typecallable, class_manager, + target_mapper, order_by, query_class=None, **kwargs): + super(DynamicAttributeImpl, self).__init__( + class_, key, typecallable, class_manager, **kwargs) self.target_mapper = target_mapper self.order_by = order_by - self.query_class = AppenderQuery + if not query_class: + self.query_class = AppenderQuery + else: + self.query_class = mixin_user_query(query_class) def get(self, state, passive=False): if passive: @@ -72,7 +77,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl): for ext in self.extensions: ext.remove(state, value, initiator or self) - + def _modified_event(self, state): state.modified = True if self.key not in state.committed_state: @@ -81,13 +86,12 @@ class DynamicAttributeImpl(attributes.AttributeImpl): # this is a hack to allow the _base.ComparableEntity fixture # to work state.dict[self.key] = True - return state.committed_state[self.key] - + def set(self, state, value, initiator): if initiator is self: return - + collection_history = self._modified_event(state) if _state_has_identity(state): old_collection = list(self.get(state)) @@ -97,11 +101,11 @@ class DynamicAttributeImpl(attributes.AttributeImpl): def delete(self, *args, **kwargs): raise NotImplementedError() - + def get_history(self, state, passive=False): c = self._get_collection_history(state, passive) return attributes.History(c.added_items, c.unchanged_items, c.deleted_items) - + def _get_collection_history(self, state, passive=False): if self.key in state.committed_state: c = state.committed_state[self.key] @@ -122,9 +126,11 @@ class DynamicAttributeImpl(attributes.AttributeImpl): self.fire_remove_event(state, value, initiator) -class AppenderQuery(Query): +class AppenderMixin(object): + query_class = None + def __init__(self, attr, state): - super(AppenderQuery, self).__init__(attr.target_mapper, None) + Query.__init__(self, attr.target_mapper, None) self.instance = state.obj() self.attr = attr @@ -167,21 +173,30 @@ class AppenderQuery(Query): passive=True).added_items) else: return self._clone(sess).count() - + def _clone(self, sess=None): - # note we're returning an entirely new Query class instance here - # without any assignment capabilities; - # the class of this query is determined by the session. + # note we're returning an entirely new Query class instance + # here without any assignment capabilities; the class of this + # query is determined by the session. instance = self.instance if sess is None: sess = object_session(instance) if sess is None: - raise sa_exc.UnboundExecutionError("Parent instance %s is not bound to a Session, and no contextual session is established; lazy load operation of attribute '%s' cannot proceed" % (mapperutil.instance_str(instance), self.attr.key)) + raise sa_exc.UnboundExecutionError( + "Parent instance %s is not bound to a Session, and no " + "contextual session is established; lazy load operation " + "of attribute '%s' cannot proceed" % ( + mapperutil.instance_str(instance), self.attr.key)) + + if self.query_class: + query = self.query_class(self.attr.target_mapper, session=sess) + else: + query = sess.query(self.attr.target_mapper) + query = query.with_parent(instance, self.attr.key) - q = sess.query(self.attr.target_mapper).with_parent(instance, self.attr.key) if self.attr.order_by: - q = q.order_by(self.attr.order_by) - return q + query = query.order_by(self.attr.order_by) + return query def append(self, item): self.attr.append(attributes.instance_state(self.instance), item, None) @@ -189,7 +204,16 @@ class AppenderQuery(Query): def remove(self, item): self.attr.remove(attributes.instance_state(self.instance), item, None) - + +class AppenderQuery(AppenderMixin, Query): + """A dynamic query that supports basic collection storage operations.""" + + +def mixin_user_query(cls): + """Return a new class with AppenderQuery functionality layered over.""" + name = 'Appender' + cls.__name__ + return type(name, (AppenderMixin, cls), {'query_class': cls}) + class CollectionHistory(object): """Overrides AttributeHistory to receive append/remove events directly.""" diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 93fae0837..ad42117e1 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -254,22 +254,15 @@ class RelationProperty(StrategizedProperty): """ def __init__(self, argument, - secondary=None, primaryjoin=None, - secondaryjoin=None, - foreign_keys=None, - uselist=None, - order_by=False, - backref=None, - _is_backref=False, - post_update=False, - cascade=False, extension=None, - viewonly=False, lazy=True, - collection_class=None, passive_deletes=False, - passive_updates=True, remote_side=None, - enable_typechecks=True, join_depth=None, - comparator_factory=None, - strategy_class=None, _local_remote_pairs=None): - + secondary=None, primaryjoin=None, secondaryjoin=None, + foreign_keys=None, uselist=None, order_by=False, backref=None, + _is_backref=False, post_update=False, cascade=False, + extension=None, viewonly=False, lazy=True, + collection_class=None, passive_deletes=False, + passive_updates=True, remote_side=None, + enable_typechecks=True, join_depth=None, + comparator_factory=None, strategy_class=None, + _local_remote_pairs=None, query_class=None): self.uselist = uselist self.argument = argument self.secondary = secondary @@ -285,7 +278,8 @@ class RelationProperty(StrategizedProperty): self.passive_updates = passive_updates self.remote_side = remote_side self.enable_typechecks = enable_typechecks - + self.query_class = query_class + self.join_depth = join_depth self.local_remote_pairs = _local_remote_pairs self.extension = extension |