diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2011-01-02 14:23:42 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2011-01-02 14:23:42 -0500 |
| commit | 350aed3fdb9f1e73e69655e53f44ca6a91c196da (patch) | |
| tree | 3d2a128667b5f6ca6d0b4e1f4865fc98aac6b60b /lib/sqlalchemy/orm | |
| parent | 71f92436bdc86f30e2c21d8f5244733601e8c39e (diff) | |
| download | sqlalchemy-350aed3fdb9f1e73e69655e53f44ca6a91c196da.tar.gz | |
- whitespace removal bonanza
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 132 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 203 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/collections.py | 24 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/dependency.py | 180 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/deprecated_interfaces.py | 98 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 96 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/dynamic.py | 14 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/events.py | 246 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/exc.py | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/identity.py | 66 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/instrumentation.py | 102 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 59 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 254 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 112 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 306 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/scoping.py | 10 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 160 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/state.py | 124 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 244 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/sync.py | 6 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/unitofwork.py | 120 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 38 |
22 files changed, 1302 insertions, 1304 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index e9f4f14f6..0b77b0239 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -143,7 +143,7 @@ def scoped_session(session_factory, scopefunc=None): def create_session(bind=None, **kwargs): """Create a new :class:`.Session` with no automation enabled by default. - + This function is used primarily for testing. The usual route to :class:`.Session` creation is via its constructor or the :func:`.sessionmaker` function. @@ -178,10 +178,10 @@ def create_session(bind=None, **kwargs): def relationship(argument, secondary=None, **kwargs): """Provide a relationship of a primary Mapper to a secondary Mapper. - + .. note:: :func:`relationship` is historically known as :func:`relation` prior to version 0.6. - + This corresponds to a parent-child or associative table relationship. The constructed class is an instance of :class:`RelationshipProperty`. @@ -212,7 +212,7 @@ def relationship(argument, secondary=None, **kwargs): for applications that make use of :func:`.attributes.get_history` which also need to know the "previous" value of the attribute. (New in 0.6.6) - + :param backref: indicates the string name of a property to be placed on the related mapper's class that will handle this relationship in the other @@ -220,7 +220,7 @@ def relationship(argument, secondary=None, **kwargs): when the mappers are configured. Can also be passed as a :func:`backref` object to control the configuration of the new relationship. - + :param back_populates: Takes a string name and has the same meaning as ``backref``, except the complementing property is **not** created automatically, @@ -263,7 +263,7 @@ def relationship(argument, secondary=None, **kwargs): * ``all`` - shorthand for "save-update,merge, refresh-expire, expunge, delete" - + :param cascade_backrefs=True: a boolean value indicating if the ``save-update`` cascade should operate along a backref event. When set to ``False`` on a @@ -273,9 +273,9 @@ def relationship(argument, secondary=None, **kwargs): set to ``False`` on a many-to-one relationship that has a one-to-many backref, appending a persistent object to the one-to-many collection on a transient object will not add the transient to the session. - + ``cascade_backrefs`` is new in 0.6.5. - + :param collection_class: a class or callable that returns a new list-holding object. will be used in place of a plain list for storing elements. @@ -288,11 +288,11 @@ def relationship(argument, secondary=None, **kwargs): :param doc: docstring which will be applied to the resulting descriptor. - + :param extension: an :class:`.AttributeExtension` instance, or list of extensions, which will be prepended to the list of attribute listeners for - the resulting descriptor placed on the class. + the resulting descriptor placed on the class. **Deprecated.** Please see :class:`.AttributeEvents`. :param foreign_keys: @@ -307,7 +307,7 @@ def relationship(argument, secondary=None, **kwargs): "foreign" in the table metadata, allowing the specification of a list of :class:`.Column` objects that should be considered part of the foreign key. - + There are only two use cases for ``foreign_keys`` - one, when it is not convenient for :class:`.Table` metadata to contain its own foreign key metadata (which should be almost never, unless reflecting a large amount of @@ -325,7 +325,7 @@ def relationship(argument, secondary=None, **kwargs): via many-to-one using local foreign keys that are not nullable, or when the reference is one-to-one or a collection that is guaranteed to have one or at least one entry. - + :param join_depth: when non-``None``, an integer value indicating how many levels deep "eager" loaders should join on a self-referring or cyclical @@ -343,7 +343,7 @@ def relationship(argument, secondary=None, **kwargs): * ``select`` - items should be loaded lazily when the property is first accessed, using a separate SELECT statement, or identity map fetch for simple many-to-one references. - + * ``immediate`` - items should be loaded as the parents are loaded, using a separate SELECT statement, or identity map fetch for simple many-to-one references. (new as of 0.6.5) @@ -352,7 +352,7 @@ def relationship(argument, secondary=None, **kwargs): that of the parent, using a JOIN or LEFT OUTER JOIN. Whether the join is "outer" or not is determined by the ``innerjoin`` parameter. - + * ``subquery`` - items should be loaded "eagerly" within the same query as that of the parent, using a second SQL statement which issues a JOIN to a subquery of the original @@ -370,18 +370,18 @@ def relationship(argument, secondary=None, **kwargs): allowing ``append()`` and ``remove()``. Changes to the collection will not be visible until flushed to the database, where it is then refetched upon iteration. - + * True - a synonym for 'select' - + * False - a synonyn for 'joined' - + * None - a synonym for 'noload' - + Detailed discussion of loader strategies is at :ref:`loading_toplevel`. - + :param load_on_pending=False: Indicates loading behavior for transient or pending parent objects. - + When set to ``True``, causes the lazy-loader to issue a query for a parent object that is not persistent, meaning it has never been flushed. This may take effect for a pending object when @@ -389,21 +389,21 @@ def relationship(argument, secondary=None, **kwargs): "attached" to a :class:`.Session` but is not part of its pending collection. Attachment of transient objects to the session without moving to the "pending" state is not a supported behavior at this time. - + Note that the load of related objects on a pending or transient object also does not trigger any attribute change events - no user-defined events will be emitted for these attributes, and if and when the object is ultimately flushed, only the user-specific foreign key attributes will be part of the modified state. - + The load_on_pending flag does not improve behavior when the ORM is used normally - object references should be constructed at the object level, not at the foreign key level, so that they are present in an ordinary way before flush() proceeds. This flag is not not intended for general use. - + New in 0.6.5. - + :param order_by: indicates the ordering that should be applied when loading these items. @@ -456,7 +456,7 @@ def relationship(argument, secondary=None, **kwargs): (i.e. SQLite, MySQL MyISAM tables). Also see the passive_updates flag on ``mapper()``. - + A future SQLAlchemy release will provide a "detect" feature for this flag. @@ -503,7 +503,7 @@ def relationship(argument, secondary=None, **kwargs): should be treated either as one-to-one or one-to-many. Its usage is optional unless delete-orphan cascade is also set on this relationship(), in which case its required (new in 0.5.2). - + :param uselist=(True|False): a boolean that indicates if this property should be loaded as a list or a scalar. In most cases, this value is determined @@ -528,9 +528,9 @@ def relationship(argument, secondary=None, **kwargs): def relation(*arg, **kw): """A synonym for :func:`relationship`.""" - + return relationship(*arg, **kw) - + def dynamic_loader(argument, secondary=None, primaryjoin=None, secondaryjoin=None, foreign_keys=None, backref=None, post_update=False, cascade=False, remote_side=None, @@ -614,11 +614,11 @@ def column_property(*args, **kwargs): it does not load immediately, and is instead loaded when the attribute is first accessed on an instance. See also :func:`~sqlalchemy.orm.deferred`. - + :param doc: optional string that will be applied as the doc on the class-bound descriptor. - + :param extension: an :class:`.AttributeExtension` @@ -634,10 +634,10 @@ def column_property(*args, **kwargs): def composite(class_, *cols, **kwargs): """Return a composite column-based property for use with a Mapper. - + See the mapping documention section :ref:`mapper_composite` for a full usage example. - + :param class\_: The "composite type" class. @@ -788,7 +788,7 @@ def mapper(class_, local_table=None, *args, **params): :param passive_updates: Indicates UPDATE behavior of foreign keys when a primary key changes on a joined-table inheritance or other joined table mapping. - + When True, it is assumed that ON UPDATE CASCADE is configured on the foreign key in the database, and that the database will handle propagation of an UPDATE from a source column to dependent rows. @@ -797,20 +797,20 @@ def mapper(class_, local_table=None, *args, **params): required for this operation. The relationship() will update the value of the attribute on related items which are locally present in the session during a flush. - + When False, it is assumed that the database does not enforce referential integrity and will not be issuing its own CASCADE operation for an update. The relationship() will issue the appropriate UPDATE statements to the database in response to the change of a referenced key, and items locally present in the session during a flush will also be refreshed. - + This flag should probably be set to False if primary key changes are expected and the database in use doesn't support CASCADE (i.e. SQLite, MySQL MyISAM tables). - + Also see the passive_updates flag on :func:`relationship()`. - + A future SQLAlchemy release will provide a "detect" feature for this flag. @@ -939,7 +939,7 @@ def comparable_property(comparator_factory, descriptor=None): from sqlalchemy.orm import mapper, comparable_property from sqlalchemy.orm.interfaces import PropComparator from sqlalchemy.sql import func - + class MyClass(object): @property def myprop(self): @@ -954,12 +954,12 @@ def comparable_property(comparator_factory, descriptor=None): Used with the ``properties`` dictionary sent to :func:`~sqlalchemy.orm.mapper`. - + Note that :func:`comparable_property` is usually not needed for basic needs. The recipe at :mod:`.derived_attributes` offers a simpler pure-Python method of achieving a similar result using class-bound attributes with SQLAlchemy expression constructs. - + :param comparator_factory: A PropComparator subclass or factory that defines operator behavior for this property. @@ -973,21 +973,21 @@ def comparable_property(comparator_factory, descriptor=None): """ return ComparableProperty(comparator_factory, descriptor) - + @sa_util.deprecated("0.7", message=":func:`.compile_mappers` " "is renamed to :func:`.configure_mappers`") def compile_mappers(): """Initialize the inter-mapper relationships of all mappers that have been defined.""" - + configure_mappers() def clear_mappers(): """Remove all mappers from all classes. - + This function removes all instrumentation from classes and disposes of their associated mappers. Once called, the classes are unmapped and can be later re-mapped with new mappers. - + :func:`.clear_mappers` is *not* for normal use, as there is literally no valid usage for it outside of very specific testing scenarios. Normally, mappers are permanent structural components of user-defined classes, and @@ -999,7 +999,7 @@ def clear_mappers(): and possibly the test suites of other ORM extension libraries which intend to test various combinations of mapper construction upon a fixed set of classes. - + """ mapperlib._COMPILE_MUTEX.acquire() try: @@ -1025,10 +1025,10 @@ def joinedload(*keys, **kw): Used with :meth:`~sqlalchemy.orm.query.Query.options`. examples:: - + # joined-load the "orders" colleciton on "User" query(User).options(joinedload(User.orders)) - + # joined-load the "keywords" collection on each "Item", # but not the "items" collection on "Order" - those # remain lazily loaded. @@ -1039,17 +1039,17 @@ def joinedload(*keys, **kw): :func:`joinedload` also accepts a keyword argument `innerjoin=True` which indicates using an inner join instead of an outer:: - + query(Order).options(joinedload(Order.user, innerjoin=True)) - + Note that the join created by :func:`joinedload` is aliased such that no other aspects of the query will affect what it loads. To use joined eager loading with a join that is constructed manually using :meth:`~sqlalchemy.orm.query.Query.join` or :func:`~sqlalchemy.orm.join`, see :func:`contains_eager`. - + See also: :func:`subqueryload`, :func:`lazyload` - + """ innerjoin = kw.pop('innerjoin', None) if innerjoin is not None: @@ -1080,7 +1080,7 @@ def joinedload_all(*keys, **kw): load in one joined eager load. Individual descriptors are accepted as arguments as well:: - + query.options(joinedload_all(User.orders, Order.items, Item.keywords)) The keyword arguments accept a flag `innerjoin=True|False` which will @@ -1102,11 +1102,11 @@ def joinedload_all(*keys, **kw): def eagerload(*args, **kwargs): """A synonym for :func:`joinedload()`.""" return joinedload(*args, **kwargs) - + def eagerload_all(*args, **kwargs): """A synonym for :func:`joinedload_all()`""" return joinedload_all(*args, **kwargs) - + def subqueryload(*keys): """Return a ``MapperOption`` that will convert the property of the given name or series of mapped attributes @@ -1115,10 +1115,10 @@ def subqueryload(*keys): Used with :meth:`~sqlalchemy.orm.query.Query.options`. examples:: - + # subquery-load the "orders" colleciton on "User" query(User).options(subqueryload(User.orders)) - + # subquery-load the "keywords" collection on each "Item", # but not the "items" collection on "Order" - those # remain lazily loaded. @@ -1128,7 +1128,7 @@ def subqueryload(*keys): query(Order).options(subqueryload_all(Order.items, Item.keywords)) See also: :func:`joinedload`, :func:`lazyload` - + """ return strategies.EagerLazyOption(keys, lazy="subquery") @@ -1147,7 +1147,7 @@ def subqueryload_all(*keys): load in one subquery eager load. Individual descriptors are accepted as arguments as well:: - + query.options(subqueryload_all(User.orders, Order.items, Item.keywords)) @@ -1155,7 +1155,7 @@ def subqueryload_all(*keys): """ return strategies.EagerLazyOption(keys, lazy="subquery", chained=True) - + def lazyload(*keys): """Return a ``MapperOption`` that will convert the property of the given name or series of mapped attributes into a lazy load. @@ -1193,16 +1193,16 @@ def noload(*keys): def immediateload(*keys): """Return a ``MapperOption`` that will convert the property of the given name or series of mapped attributes into an immediate load. - + Used with :meth:`~sqlalchemy.orm.query.Query.options`. See also: :func:`lazyload`, :func:`eagerload`, :func:`subqueryload` - + New as of verison 0.6.5. - + """ return strategies.EagerLazyOption(keys, lazy='immediate') - + def contains_alias(alias): """Return a ``MapperOption`` that will indicate to the query that the main table has been aliased. @@ -1222,11 +1222,11 @@ def contains_eager(*keys, **kwargs): The option is used in conjunction with an explicit join that loads the desired rows, i.e.:: - + sess.query(Order).\\ join(Order.user).\\ options(contains_eager(Order.user)) - + The above query would join from the ``Order`` entity to its related ``User`` entity, and the returned ``Order`` objects would have the ``Order.user`` attribute pre-populated. @@ -1235,7 +1235,7 @@ def contains_eager(*keys, **kwargs): string name of an alias, an :func:`~sqlalchemy.sql.expression.alias` construct, or an :func:`~sqlalchemy.orm.aliased` construct. Use this when the eagerly-loaded rows are to come from an aliased table:: - + user_alias = aliased(User) sess.query(Order).\\ join((user_alias, Order.user)).\\ diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 56cae6a18..6b57d33f5 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -53,7 +53,7 @@ PASSIVE_OFF = False #util.symbol('PASSIVE_OFF') class QueryableAttribute(interfaces.PropComparator): """Base class for class-bound attributes. """ - + def __init__(self, class_, key, impl=None, comparator=None, parententity=None): self.class_ = class_ @@ -73,15 +73,15 @@ class QueryableAttribute(interfaces.PropComparator): dispatch = event.dispatcher(events.AttributeEvents) dispatch.dispatch_cls._active_history = False - + @util.memoized_property def _supports_population(self): return self.impl.supports_population - + def get_history(self, instance, **kwargs): return self.impl.get_history(instance_state(instance), instance_dict(instance), **kwargs) - + def __selectable__(self): # TODO: conditionally attach this method based on clause_element ? return self @@ -100,7 +100,7 @@ class QueryableAttribute(interfaces.PropComparator): def hasparent(self, state, optimistic=False): return self.impl.hasparent(state, optimistic=optimistic) - + def __getattr__(self, key): try: return getattr(self.comparator, key) @@ -111,7 +111,7 @@ class QueryableAttribute(interfaces.PropComparator): type(self.comparator).__name__, key) ) - + def __str__(self): return repr(self.parententity) + "." + self.property.key @@ -146,15 +146,15 @@ def create_proxied_attribute(descriptor): Returns a new QueryableAttribute type that delegates descriptor behavior and getattr() to the given descriptor. """ - + # TODO: can move this to descriptor_props if the need for this # function is removed from ext/hybrid.py - + class Proxy(QueryableAttribute): """Presents the :class:`.QueryableAttribute` interface as a proxy on top of a Python descriptor / :class:`.PropComparator` combination. - + """ def __init__(self, class_, key, descriptor, comparator, @@ -165,7 +165,7 @@ def create_proxied_attribute(descriptor): self._comparator = comparator self.adapter = adapter self.__doc__ = doc - + @util.memoized_property def comparator(self): if util.callable(self._comparator): @@ -173,20 +173,20 @@ def create_proxied_attribute(descriptor): if self.adapter: self._comparator = self._comparator.adapted(self.adapter) return self._comparator - + def __get__(self, instance, owner): if instance is None: return self else: return self.descriptor.__get__(instance, owner) - + def __str__(self): return self.key - + def __getattr__(self, attribute): """Delegate __getattr__ to the original descriptor and/or comparator.""" - + try: return getattr(descriptor, attribute) except AttributeError: @@ -219,7 +219,7 @@ class AttributeImpl(object): \class_ associated class - + key string name of the attribute @@ -251,12 +251,12 @@ class AttributeImpl(object): the hasparent() function to identify an "owning" attribute. Allows multiple AttributeImpls to all match a single owner attribute. - + expire_missing if False, don't add an "expiry" callable to this attribute during state.expire_attributes(None), if no value is present for this key. - + """ self.class_ = class_ self.key = key @@ -268,30 +268,30 @@ class AttributeImpl(object): self.is_equal = operator.eq else: self.is_equal = compare_function - + # TODO: pass in the manager here # instead of doing a lookup attr = manager_of_class(class_)[key] - + for ext in util.to_list(extension or []): ext._adapt_listener(attr, ext) - + if active_history: self.dispatch._active_history = True self.expire_missing = expire_missing - + def _get_active_history(self): """Backwards compat for impl.active_history""" - + return self.dispatch._active_history - + def _set_active_history(self, value): self.dispatch._active_history = value - + active_history = property(_get_active_history, _set_active_history) - - + + def hasparent(self, state, optimistic=False): """Return the boolean value of a `hasparent` flag attached to the given state. @@ -337,17 +337,17 @@ class AttributeImpl(object): def get_history(self, state, dict_, passive=PASSIVE_OFF): raise NotImplementedError() - + def get_all_pending(self, state, dict_): """Return a list of tuples of (state, obj) for all objects in this attribute's current state + history. - + Only applies to object-based attributes. This is an inlining of existing functionality which roughly correponds to: - + get_state_history( state, key, @@ -355,7 +355,7 @@ class AttributeImpl(object): """ raise NotImplementedError() - + def initialize(self, state, dict_): """Initialize the given state's attribute with an empty value.""" @@ -379,7 +379,7 @@ class AttributeImpl(object): state.committed_state[key] is NEVER_SET: if passive is PASSIVE_NO_INITIALIZE: return PASSIVE_NO_RESULT - + if key in state.callables: callable_ = state.callables[key] value = callable_(passive) @@ -404,7 +404,7 @@ class AttributeImpl(object): # Return a new, empty value return self.initialize(state, dict_) - + def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF): self.set(state, dict_, value, initiator, passive=passive) @@ -515,7 +515,7 @@ class MutableScalarAttributeImpl(ScalarAttributeImpl): v = state.committed_state.get(self.key, NO_VALUE) else: v = dict_.get(self.key, NO_VALUE) - + return History.from_scalar_attribute(self, state, v) def check_mutable_modified(self, state, dict_): @@ -545,7 +545,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): where the target object is also instrumented. Adds events to delete/set operations. - + """ accepts_scalar_loader = False @@ -585,12 +585,12 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): ret = [(instance_state(current), current)] else: ret = [] - + if self.key in state.committed_state: original = state.committed_state[self.key] if original not in (NEVER_SET, PASSIVE_NO_RESULT, None) and \ original is not current: - + ret.append((instance_state(original), original)) return ret else: @@ -611,14 +611,14 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT) else: old = self.get(state, dict_, passive=PASSIVE_NO_FETCH) - + value = self.fire_replace_event(state, dict_, value, old, initiator) dict_[self.key] = value def fire_remove_event(self, state, dict_, value, initiator): if self.trackparent and value is not None: self.sethasparent(instance_state(value), False) - + for fn in self.dispatch.remove: fn(state, value, initiator or self) @@ -630,7 +630,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): previous is not None and previous is not PASSIVE_NO_RESULT): self.sethasparent(instance_state(previous), False) - + for fn in self.dispatch.set: value = fn(state, value, previous, initiator or self) @@ -691,24 +691,24 @@ class CollectionAttributeImpl(AttributeImpl): current = dict_[self.key] current = getattr(current, '_sa_adapter') - + if self.key in state.committed_state: original = state.committed_state[self.key] if original is not NO_VALUE: current_states = [(instance_state(c), c) for c in current] original_states = [(instance_state(c), c) for c in original] - + current_set = dict(current_states) original_set = dict(original_states) - + return \ [(s, o) for s, o in current_states if s not in original_set] + \ [(s, o) for s, o in current_states if s in original_set] + \ [(s, o) for s, o in original_states if s not in current_set] - + return [(instance_state(o), o) for o in current] - + def fire_append_event(self, state, dict_, value, initiator): for fn in self.dispatch.append: value = fn(state, value, initiator or self) @@ -844,7 +844,7 @@ class CollectionAttributeImpl(AttributeImpl): state.commit(dict_, [self.key]) if self.key in state.pending: - + # pending items exist. issue a modified event, # add/remove new items. state.modified_event(dict_, self, user_data, True) @@ -893,7 +893,7 @@ def backref_listeners(attribute, key, uselist): initiator, passive=PASSIVE_NO_FETCH) except (ValueError, KeyError, IndexError): pass - + if child is not None: child_state, child_dict = instance_state(child),\ instance_dict(child) @@ -926,19 +926,19 @@ def backref_listeners(attribute, key, uselist): state.obj(), initiator, passive=PASSIVE_NO_FETCH) - + if uselist: event.listen(attribute, "append", append, retval=True, raw=True) else: event.listen(attribute, "set", set_, retval=True, raw=True) # TODO: need coverage in test/orm/ of remove event event.listen(attribute, "remove", remove, retval=True, raw=True) - + class History(tuple): """A 3-tuple of added, unchanged and deleted values, representing the changes which have occured on an instrumented attribute. - + Each tuple member is an iterable sequence. """ @@ -948,57 +948,57 @@ class History(tuple): added = property(itemgetter(0)) """Return the collection of items added to the attribute (the first tuple element).""" - + unchanged = property(itemgetter(1)) """Return the collection of items that have not changed on the attribute (the second tuple element).""" - - + + deleted = property(itemgetter(2)) """Return the collection of items that have been removed from the attribute (the third tuple element).""" - + def __new__(cls, added, unchanged, deleted): return tuple.__new__(cls, (added, unchanged, deleted)) - + def __nonzero__(self): return self != HISTORY_BLANK - + def empty(self): """Return True if this :class:`History` has no changes and no existing, unchanged state. - + """ - + return not bool( (self.added or self.deleted) or self.unchanged and self.unchanged != [None] ) - + def sum(self): """Return a collection of added + unchanged + deleted.""" - + return (self.added or []) +\ (self.unchanged or []) +\ (self.deleted or []) - + def non_deleted(self): """Return a collection of added + unchanged.""" - + return (self.added or []) +\ (self.unchanged or []) - + def non_added(self): """Return a collection of unchanged + deleted.""" - + return (self.unchanged or []) +\ (self.deleted or []) - + def has_changes(self): """Return True if this :class:`History` has changes.""" - + return bool(self.added or self.deleted) - + def as_state(self): return History( [(c is not None and c is not PASSIVE_NO_RESULT) @@ -1039,7 +1039,7 @@ class History(tuple): @classmethod def from_object_attribute(cls, attribute, state, current): original = state.committed_state.get(attribute.key, NEVER_SET) - + if current is NO_VALUE: if (original is not None and original is not NEVER_SET and @@ -1064,7 +1064,7 @@ class History(tuple): def from_collection(cls, attribute, state, current): original = state.committed_state.get(attribute.key, NEVER_SET) current = getattr(current, '_sa_adapter') - + if original is NO_VALUE: return cls(list(current), (), ()) elif original is NEVER_SET: @@ -1072,10 +1072,10 @@ class History(tuple): else: current_states = [(instance_state(c), c) for c in current] original_states = [(instance_state(c), c) for c in original] - + current_set = dict(current_states) original_set = dict(original_states) - + return cls( [o for s, o in current_states if s not in original_set], [o for s, o in current_states if s in original_set], @@ -1087,25 +1087,25 @@ HISTORY_BLANK = History(None, None, None) def get_history(obj, key, **kwargs): """Return a :class:`.History` record for the given object and attribute key. - + :param obj: an object whose class is instrumented by the - attributes package. - + attributes package. + :param key: string attribute name. - + :param kwargs: Optional keyword arguments currently include the ``passive`` flag, which indicates if the attribute should be loaded from the database if not already present (:attr:`PASSIVE_NO_FETCH`), and if the attribute should be not initialized to a blank value otherwise (:attr:`PASSIVE_NO_INITIALIZE`). Default is :attr:`PASSIVE_OFF`. - + """ return get_state_history(instance_state(obj), key, **kwargs) def get_state_history(state, key, **kwargs): return state.get_history(key, **kwargs) - + def has_parent(cls, obj, key, optimistic=False): """TODO""" manager = manager_of_class(cls) @@ -1120,12 +1120,12 @@ def register_attribute(class_, key, **kw): comparator, parententity, doc=doc) register_attribute_impl(class_, key, **kw) return desc - -def register_attribute_impl(class_, key, + +def register_attribute_impl(class_, key, uselist=False, callable_=None, useobject=False, mutable_scalars=False, impl_class=None, backref=None, **kw): - + manager = manager_of_class(class_) if uselist: factory = kw.pop('typecallable', None) @@ -1135,7 +1135,7 @@ def register_attribute_impl(class_, key, typecallable = kw.pop('typecallable', None) dispatch = manager[key].dispatch - + if impl_class: impl = impl_class(class_, key, typecallable, dispatch, **kw) elif uselist: @@ -1151,22 +1151,22 @@ def register_attribute_impl(class_, key, impl = ScalarAttributeImpl(class_, key, callable_, dispatch, **kw) manager[key].impl = impl - + if backref: backref_listeners(manager[key], backref, uselist) manager.post_configure_attribute(key) return manager[key] - + def register_descriptor(class_, key, comparator=None, parententity=None, property_=None, doc=None): manager = manager_of_class(class_) descriptor = InstrumentedAttribute(class_, key, comparator=comparator, parententity=parententity) - + descriptor.__doc__ = doc - + manager.instrument_attribute(key, descriptor) return descriptor @@ -1175,36 +1175,36 @@ def unregister_attribute(class_, key): def init_collection(obj, key): """Initialize a collection attribute and return the collection adapter. - + This function is used to provide direct access to collection internals for a previously unloaded attribute. e.g.:: - + collection_adapter = init_collection(someobject, 'elements') for elem in values: collection_adapter.append_without_event(elem) - + For an easier way to do the above, see :func:`~sqlalchemy.orm.attributes.set_committed_value`. - + obj is an instrumented object instance. An InstanceState is accepted directly for backwards compatibility but this usage is deprecated. - + """ state = instance_state(obj) dict_ = state.dict return init_state_collection(state, dict_, key) - + def init_state_collection(state, dict_, key): """Initialize a collection attribute and return the collection adapter.""" - + attr = state.manager[key].impl user_data = attr.initialize(state, dict_) return attr.get_collection(state, dict_, user_data) def set_committed_value(instance, key, value): """Set the value of an attribute with no history events. - + Cancels any previous history present. The value should be a scalar value for scalar-holding attributes, or an iterable for any collection-holding attribute. @@ -1215,20 +1215,20 @@ def set_committed_value(instance, key, value): which has loaded additional attributes or collections through separate queries, which can then be attached to an instance as though it were part of its original loaded state. - + """ state, dict_ = instance_state(instance), instance_dict(instance) state.manager[key].impl.set_committed_value(state, dict_, value) - + def set_attribute(instance, key, value): """Set the value of an attribute, firing history events. - + This function may be used regardless of instrumentation applied directly to the class, i.e. no descriptors are required. Custom attribute management schemes will need to make usage of this method to establish attribute state as understood by SQLAlchemy. - + """ state, dict_ = instance_state(instance), instance_dict(instance) state.manager[key].impl.set(state, dict_, value, None) @@ -1241,7 +1241,7 @@ def get_attribute(instance, key): Custom attribute management schemes will need to make usage of this method to make usage of attribute state as understood by SQLAlchemy. - + """ state, dict_ = instance_state(instance), instance_dict(instance) return state.manager[key].impl.get(state, dict_) @@ -1254,20 +1254,19 @@ def del_attribute(instance, key): Custom attribute management schemes will need to make usage of this method to establish attribute state as understood by SQLAlchemy. - + """ state, dict_ = instance_state(instance), instance_dict(instance) state.manager[key].impl.delete(state, dict_) def flag_modified(instance, key): """Mark an attribute on an instance as 'modified'. - + This sets the 'modified' flag on the instance and establishes an unconditional change event for the given attribute. - + """ state, dict_ = instance_state(instance), instance_dict(instance) impl = state.manager[key].impl state.modified_event(dict_, impl, NO_VALUE) - -
\ No newline at end of file + diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py index b0fab36c0..4b03a50db 100644 --- a/lib/sqlalchemy/orm/collections.py +++ b/lib/sqlalchemy/orm/collections.py @@ -449,7 +449,7 @@ class collection(object): # implementations def collection_adapter(collection): """Fetch the :class:`.CollectionAdapter` for a collection.""" - + return getattr(collection, '_sa_adapter', None) def collection_iter(collection): @@ -479,14 +479,14 @@ class CollectionAdapter(object): The usage of getattr()/setattr() is currently to allow injection of custom methods, such as to unwrap Zope security proxies. - + """ def __init__(self, attr, owner_state, data): self._key = attr.key self._data = weakref.ref(data) self.owner_state = owner_state self.link_to_self(data) - + @property def data(self): "The entity collection being adapted." @@ -495,7 +495,7 @@ class CollectionAdapter(object): @util.memoized_property def attr(self): return self.owner_state.manager[self._key].impl - + def link_to_self(self, data): """Link a collection to this adapter, and fire a link event.""" setattr(data, '_sa_adapter', self) @@ -555,7 +555,7 @@ class CollectionAdapter(object): def append_with_event(self, item, initiator=None): """Add an entity to the collection, firing mutation events.""" - + getattr(self._data(), '_sa_appender')(item, _sa_initiator=initiator) def append_without_event(self, item): @@ -578,7 +578,7 @@ class CollectionAdapter(object): def clear_with_event(self, initiator=None): """Empty the collection, firing a mutation event for each entity.""" - + remover = getattr(self._data(), '_sa_remover') for item in list(self): remover(item, _sa_initiator=initiator) @@ -592,7 +592,7 @@ class CollectionAdapter(object): def __iter__(self): """Iterate over entities in the collection.""" - + # Py3K requires iter() here return iter(getattr(self._data(), '_sa_iterator')()) @@ -926,7 +926,7 @@ def __set(collection, item, _sa_initiator=None): if executor: item = getattr(executor, 'fire_append_event')(item, _sa_initiator) return item - + def __del(collection, item, _sa_initiator=None): """Run del events, may eventually be inlined into decorators.""" if _sa_initiator is not False and item is not None: @@ -987,12 +987,12 @@ def _list_decorators(): stop = index.stop or len(self) if stop < 0: stop += len(self) - + if step == 1: for i in xrange(start, stop, step): if len(self) > start: del self[start] - + for i, item in enumerate(value): self.insert(i + start, item) else: @@ -1041,7 +1041,7 @@ def _list_decorators(): _tidy(__delslice__) return __delslice__ # end Py2K - + def extend(fn): def extend(self, iterable): for value in iterable: @@ -1371,7 +1371,7 @@ class InstrumentedDict(dict): __instrumentation__ = { 'iterator': 'itervalues', } # end Py2K - + __canned_instrumentation = { list: InstrumentedList, set: InstrumentedSet, diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 57c6d6e9e..8acf77ad8 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -33,29 +33,29 @@ class DependencyProcessor(object): "No target attributes to populate between parent and " "child are present" % self.prop) - + @classmethod def from_relationship(cls, prop): return _direction_to_processor[prop.direction](prop) - + def hasparent(self, state): """return True if the given object instance has a parent, according to the ``InstrumentedAttribute`` handled by this ``DependencyProcessor``. - + """ return self.parent.class_manager.get_impl(self.key).hasparent(state) def per_property_preprocessors(self, uow): """establish actions and dependencies related to a flush. - + These actions will operate on all relevant states in the aggreagte. - + """ uow.register_preprocessor(self, True) - - + + def per_property_flush_actions(self, uow): after_save = unitofwork.ProcessAll(uow, self, False, True) before_delete = unitofwork.ProcessAll(uow, self, True, True) @@ -77,7 +77,7 @@ class DependencyProcessor(object): uow, self.mapper.primary_base_mapper ) - + self.per_property_dependencies(uow, parent_saves, child_saves, @@ -86,15 +86,15 @@ class DependencyProcessor(object): after_save, before_delete ) - + def per_state_flush_actions(self, uow, states, isdelete): """establish actions and dependencies related to a flush. - + These actions will operate on all relevant states individually. This occurs only if there are cycles in the 'aggregated' version of events. - + """ parent_base_mapper = self.parent.primary_base_mapper @@ -104,7 +104,7 @@ class DependencyProcessor(object): # locate and disable the aggregate processors # for this dependency - + if isdelete: before_delete = unitofwork.ProcessAll(uow, self, True, True) before_delete.disabled = True @@ -113,14 +113,14 @@ class DependencyProcessor(object): after_save.disabled = True # check if the "child" side is part of the cycle - + if child_saves not in uow.cycles: # based on the current dependencies we use, the saves/ # deletes should always be in the 'cycles' collection # together. if this changes, we will have to break up # this method a bit more. assert child_deletes not in uow.cycles - + # child side is not part of the cycle, so we will link per-state # actions to the aggregate "saves", "deletes" actions child_actions = [ @@ -129,7 +129,7 @@ class DependencyProcessor(object): child_in_cycles = False else: child_in_cycles = True - + # check if the "parent" side is part of the cycle if not isdelete: parent_saves = unitofwork.SaveUpdateAll( @@ -145,14 +145,14 @@ class DependencyProcessor(object): parent_saves = after_save = None if parent_deletes in uow.cycles: parent_in_cycles = True - + # now create actions /dependencies for each state. for state in states: # detect if there's anything changed or loaded # by a preprocessor on this state/attribute. if not, # we should be able to skip it entirely. sum_ = state.manager[self.key].impl.get_all_pending(state, state.dict) - + if not sum_: continue @@ -171,7 +171,7 @@ class DependencyProcessor(object): uow, state, parent_base_mapper) - + if child_in_cycles: child_actions = [] for child_state, child in sum_: @@ -192,7 +192,7 @@ class DependencyProcessor(object): child_base_mapper), False) child_actions.append(child_action) - + # establish dependencies between our possibly per-state # parent action and our possibly per-state child action. for child_action, childisdelete in child_actions: @@ -201,23 +201,23 @@ class DependencyProcessor(object): child_action, after_save, before_delete, isdelete, childisdelete) - - + + def presort_deletes(self, uowcommit, states): return False - + def presort_saves(self, uowcommit, states): return False - + def process_deletes(self, uowcommit, states): pass - + def process_saves(self, uowcommit, states): pass def prop_has_changes(self, uowcommit, states, isdelete): passive = not isdelete or self.passive_deletes - + for s in states: # TODO: add a high speed method # to InstanceState which returns: attribute @@ -230,7 +230,7 @@ class DependencyProcessor(object): return True else: return False - + def _verify_canload(self, state): if state is not None and \ not self.mapper._canload(state, @@ -249,7 +249,7 @@ class DependencyProcessor(object): "Attempting to flush an item of type %s on collection '%s', " "whose mapper does not inherit from that of %s." % (state.class_, self.prop, self.mapper.class_)) - + def _synchronize(self, state, child, associationrow, clearkeys, uowcommit): raise NotImplementedError() @@ -275,7 +275,7 @@ class DependencyProcessor(object): [r for l, r in self.prop.synchronize_pairs] ) break - + def _pks_changed(self, uowcommit, state): raise NotImplementedError() @@ -283,7 +283,7 @@ class DependencyProcessor(object): return "%s(%s)" % (self.__class__.__name__, self.prop) class OneToManyDP(DependencyProcessor): - + def per_property_dependencies(self, uow, parent_saves, child_saves, parent_deletes, @@ -300,37 +300,37 @@ class OneToManyDP(DependencyProcessor): uow, self.mapper.primary_base_mapper, True) - + uow.dependencies.update([ (child_saves, after_save), (parent_saves, after_save), (after_save, child_post_updates), - + (before_delete, child_pre_updates), (child_pre_updates, parent_deletes), (child_pre_updates, child_deletes), - + ]) else: uow.dependencies.update([ (parent_saves, after_save), (after_save, child_saves), (after_save, child_deletes), - + (child_saves, parent_deletes), (child_deletes, parent_deletes), (before_delete, child_saves), (before_delete, child_deletes), ]) - + def per_state_dependencies(self, uow, save_parent, delete_parent, child_action, after_save, before_delete, isdelete, childisdelete): - + if self.post_update: child_post_updates = unitofwork.IssuePostUpdate( @@ -341,7 +341,7 @@ class OneToManyDP(DependencyProcessor): uow, self.mapper.primary_base_mapper, True) - + # TODO: this whole block is not covered # by any tests if not isdelete: @@ -378,7 +378,7 @@ class OneToManyDP(DependencyProcessor): (before_delete, child_action), (child_action, delete_parent) ]) - + def presort_deletes(self, uowcommit, states): # head object is being deleted, and we manage its list of # child objects the child objects have to have their @@ -398,21 +398,21 @@ class OneToManyDP(DependencyProcessor): uowcommit.register_object(child, isdelete=True) else: uowcommit.register_object(child) - + if should_null_fks: for child in history.unchanged: if child is not None: uowcommit.register_object(child, operation="delete", prop=self.prop) - - + + def presort_saves(self, uowcommit, states): children_added = uowcommit.memo(('children_added', self), set) - + for state in states: pks_changed = self._pks_changed(uowcommit, state) - + history = uowcommit.get_attribute_history( state, self.key, @@ -451,14 +451,14 @@ class OneToManyDP(DependencyProcessor): self.passive_updates, operation="pk change", prop=self.prop) - + def process_deletes(self, uowcommit, states): # head object is being deleted, and we manage its list of # child objects the child objects have to have their foreign # key to the parent set to NULL this phase can be called # safely for any cascade but is unnecessary if delete cascade # is on. - + if self.post_update or not self.passive_deletes == 'all': children_added = uowcommit.memo(('children_added', self), set) @@ -478,7 +478,7 @@ class OneToManyDP(DependencyProcessor): uowcommit, False) if self.post_update and child: self._post_update(child, uowcommit, [state]) - + if self.post_update or not self.cascade.delete: for child in set(history.unchanged).\ difference(children_added): @@ -492,12 +492,12 @@ class OneToManyDP(DependencyProcessor): self._post_update(child, uowcommit, [state]) - + # technically, we can even remove each child from the # collection here too. but this would be a somewhat # inconsistent behavior since it wouldn't happen #if the old parent wasn't deleted but child was moved. - + def process_saves(self, uowcommit, states): for state in states: history = uowcommit.get_attribute_history(state, @@ -520,7 +520,7 @@ class OneToManyDP(DependencyProcessor): for child in history.unchanged: self._synchronize(state, child, None, False, uowcommit, True) - + def _synchronize(self, state, child, associationrow, clearkeys, uowcommit, pks_changed): @@ -593,7 +593,7 @@ class ManyToOneDP(DependencyProcessor): isdelete, childisdelete): if self.post_update: - + if not isdelete: parent_post_updates = unitofwork.IssuePostUpdate( uow, @@ -608,7 +608,7 @@ class ManyToOneDP(DependencyProcessor): uow.dependencies.update([ (save_parent, after_save), (child_action, after_save), - + (after_save, parent_post_updates) ]) else: @@ -622,7 +622,7 @@ class ManyToOneDP(DependencyProcessor): (parent_pre_updates, delete_parent), (parent_pre_updates, child_action) ]) - + elif not isdelete: if not childisdelete: uow.dependencies.update([ @@ -633,7 +633,7 @@ class ManyToOneDP(DependencyProcessor): uow.dependencies.update([ (after_save, save_parent), ]) - + else: if childisdelete: uow.dependencies.update([ @@ -661,7 +661,7 @@ class ManyToOneDP(DependencyProcessor): 'delete', child): uowcommit.register_object( st_, isdelete=True) - + def presort_saves(self, uowcommit, states): for state in states: uowcommit.register_object(state, operation="add", prop=self.prop) @@ -676,7 +676,7 @@ class ManyToOneDP(DependencyProcessor): if self.hasparent(child) is False: uowcommit.register_object(child, isdelete=True, operation="delete", prop=self.prop) - + for c, m, st_, dct_ in self.mapper.cascade_iterator( 'delete', child): uowcommit.register_object( @@ -687,7 +687,7 @@ class ManyToOneDP(DependencyProcessor): if self.post_update and \ not self.cascade.delete_orphan and \ not self.passive_deletes == 'all': - + # post_update means we have to update our # row to not reference the child object # before we can DELETE the row @@ -710,7 +710,7 @@ class ManyToOneDP(DependencyProcessor): for child in history.added: self._synchronize(state, child, None, False, uowcommit, "add") - + if self.post_update: self._post_update(state, uowcommit, history.sum()) @@ -728,7 +728,7 @@ class ManyToOneDP(DependencyProcessor): "operation along '%s' won't proceed" % (mapperutil.state_class_str(child), operation, self.prop)) return - + if clearkeys or child is None: sync.clear(state, self.parent, self.prop.synchronize_pairs) else: @@ -743,12 +743,12 @@ class DetectKeySwitch(DependencyProcessor): """For many-to-one relationships with no one-to-many backref, searches for parents through the unit of work when a primary key has changed and updates them. - + Theoretically, this approach could be expanded to support transparent deletion of objects referenced via many-to-one as well, although the current attribute system doesn't do enough bookkeeping for this to be efficient. - + """ def per_property_preprocessors(self, uow): @@ -759,7 +759,7 @@ class DetectKeySwitch(DependencyProcessor): if False in (prop.passive_updates for \ prop in self.prop._reverse_property): return - + uow.register_preprocessor(self, False) def per_property_flush_actions(self, uow): @@ -770,10 +770,10 @@ class DetectKeySwitch(DependencyProcessor): uow.dependencies.update([ (parent_saves, after_save) ]) - + def per_state_flush_actions(self, uow, states, isdelete): pass - + def presort_deletes(self, uowcommit, states): pass @@ -787,9 +787,9 @@ class DetectKeySwitch(DependencyProcessor): if not isdelete and self.passive_updates: d = self._key_switchers(uow, states) return bool(d) - + return False - + def process_deletes(self, uowcommit, states): assert False @@ -800,13 +800,13 @@ class DetectKeySwitch(DependencyProcessor): # statements being emitted assert self.passive_updates self._process_key_switches(states, uowcommit) - + def _key_switchers(self, uow, states): switched, notswitched = uow.memo( ('pk_switchers', self), lambda: (set(), set()) ) - + allstates = switched.union(notswitched) for s in states: if s not in allstates: @@ -815,7 +815,7 @@ class DetectKeySwitch(DependencyProcessor): else: notswitched.add(s) return switched - + def _process_key_switches(self, deplist, uowcommit): switchers = self._key_switchers(uowcommit, deplist) if switchers: @@ -848,7 +848,7 @@ class DetectKeySwitch(DependencyProcessor): class ManyToManyDP(DependencyProcessor): - + def per_property_dependencies(self, uow, parent_saves, child_saves, parent_deletes, @@ -861,14 +861,14 @@ class ManyToManyDP(DependencyProcessor): (parent_saves, after_save), (child_saves, after_save), (after_save, child_deletes), - + # a rowswitch on the parent from deleted to saved # can make this one occur, as the "save" may remove # an element from the # "deleted" list before we have a chance to # process its child rows (before_delete, parent_saves), - + (before_delete, parent_deletes), (before_delete, child_deletes), (before_delete, child_saves), @@ -896,7 +896,7 @@ class ManyToManyDP(DependencyProcessor): (before_delete, child_action), (before_delete, delete_parent) ]) - + def presort_deletes(self, uowcommit, states): if not self.passive_deletes: # if no passive deletes, load history on @@ -907,7 +907,7 @@ class ManyToManyDP(DependencyProcessor): state, self.key, passive=self.passive_deletes) - + def presort_saves(self, uowcommit, states): if not self.passive_updates: # if no passive updates, load history on @@ -922,7 +922,7 @@ class ManyToManyDP(DependencyProcessor): if not self.cascade.delete_orphan: return - + # check for child items removed from the collection # if delete_orphan check is turned on. for state in states: @@ -940,12 +940,12 @@ class ManyToManyDP(DependencyProcessor): child): uowcommit.register_object( st_, isdelete=True) - + def process_deletes(self, uowcommit, states): secondary_delete = [] secondary_insert = [] secondary_update = [] - + processed = self._get_reversed_processed_set(uowcommit) tmp = set() for state in states: @@ -969,12 +969,12 @@ class ManyToManyDP(DependencyProcessor): False, uowcommit, "delete"): continue secondary_delete.append(associationrow) - + tmp.update((c, state) for c in history.non_added()) if processed is not None: processed.update(tmp) - + self._run_crud(uowcommit, secondary_insert, secondary_update, secondary_delete) @@ -1016,12 +1016,12 @@ class ManyToManyDP(DependencyProcessor): False, uowcommit, "delete"): continue secondary_delete.append(associationrow) - + tmp.update((c, state) for c in history.added + history.deleted) - + if need_cascade_pks: - + for child in history.unchanged: associationrow = {} sync.update(state, @@ -1036,17 +1036,17 @@ class ManyToManyDP(DependencyProcessor): self.prop.secondary_synchronize_pairs) secondary_update.append(associationrow) - + if processed is not None: processed.update(tmp) - + self._run_crud(uowcommit, secondary_insert, secondary_update, secondary_delete) - + def _run_crud(self, uowcommit, secondary_insert, secondary_update, secondary_delete): connection = uowcommit.transaction.connection(self.mapper) - + if secondary_delete: associationrow = secondary_delete[0] statement = self.secondary.delete(sql.and_(*[ @@ -1055,7 +1055,7 @@ class ManyToManyDP(DependencyProcessor): if c.key in associationrow ])) result = connection.execute(statement, secondary_delete) - + if result.supports_sane_multi_rowcount() and \ result.rowcount != len(secondary_delete): raise exc.StaleDataError( @@ -1085,7 +1085,7 @@ class ManyToManyDP(DependencyProcessor): if secondary_insert: statement = self.secondary.insert() connection.execute(statement, secondary_insert) - + def _synchronize(self, state, child, associationrow, clearkeys, uowcommit, operation): if associationrow is None: @@ -1098,16 +1098,16 @@ class ManyToManyDP(DependencyProcessor): "operation along '%s' won't proceed" % (mapperutil.state_class_str(child), operation, self.prop)) return False - + self._verify_canload(child) - + sync.populate_dict(state, self.parent, associationrow, self.prop.synchronize_pairs) sync.populate_dict(child, self.mapper, associationrow, self.prop.secondary_synchronize_pairs) - + return True - + def _pks_changed(self, uowcommit, state): return sync.source_modified( uowcommit, diff --git a/lib/sqlalchemy/orm/deprecated_interfaces.py b/lib/sqlalchemy/orm/deprecated_interfaces.py index 341594578..8cdde2282 100644 --- a/lib/sqlalchemy/orm/deprecated_interfaces.py +++ b/lib/sqlalchemy/orm/deprecated_interfaces.py @@ -14,27 +14,27 @@ class MapperExtension(object): .. note:: :class:`.MapperExtension` is deprecated. Please refer to :func:`.event.listen` as well as :class:`.MapperEvents`. - + New extension classes subclass :class:`.MapperExtension` and are specified using the ``extension`` mapper() argument, which is a single :class:`.MapperExtension` or a list of such:: - + from sqlalchemy.orm.interfaces import MapperExtension - + class MyExtension(MapperExtension): def before_insert(self, mapper, connection, instance): print "instance %s before insert !" % instance - + m = mapper(User, users_table, extension=MyExtension()) - + A single mapper can maintain a chain of ``MapperExtension`` objects. When a particular mapping event occurs, the corresponding method on each ``MapperExtension`` is invoked serially, and each method has the ability to halt the chain from proceeding further:: - + m = mapper(User, users_table, extension=[ext1, ext2, ext3]) - + Each ``MapperExtension`` method returns the symbol EXT_CONTINUE by default. This symbol generally means "move to the next ``MapperExtension`` for processing". For methods @@ -43,13 +43,13 @@ class MapperExtension(object): should be ignored. In some cases it's required for a default mapper activity to be performed, such as adding a new instance to a result list. - + The symbol EXT_STOP has significance within a chain of ``MapperExtension`` objects that the chain will be stopped when this symbol is returned. Like EXT_CONTINUE, it also has additional significance in some cases that a default mapper activity will not be performed. - + """ @classmethod @@ -75,17 +75,17 @@ class MapperExtension(object): 'before_delete', 'after_delete' )) - + @classmethod def _adapt_listener_methods(cls, self, listener, methods): - + for meth in methods: me_meth = getattr(MapperExtension, meth) ls_meth = getattr(listener, meth) - + # TODO: comparing self.methods to cls.method, # this comparison is probably moot - + if me_meth is not ls_meth: if meth == 'reconstruct_instance': def go(ls_meth): @@ -109,7 +109,7 @@ class MapperExtension(object): util.warn_exception(ls_meth, self, self.class_, self.class_manager.original_init, instance, args, kwargs) - + return init_failed event.listen(self.class_manager, 'init_failure', go(ls_meth), raw=False, propagate=True) @@ -121,20 +121,20 @@ class MapperExtension(object): def instrument_class(self, mapper, class_): """Receive a class when the mapper is first constructed, and has applied instrumentation to the mapped class. - + The return value is only significant within the ``MapperExtension`` chain; the parent mapper's behavior isn't modified by this method. - + """ return EXT_CONTINUE def init_instance(self, mapper, class_, oldinit, instance, args, kwargs): """Receive an instance when it's constructor is called. - + This method is only called during a userland construction of an object. It is not called when an object is loaded from the database. - + The return value is only significant within the ``MapperExtension`` chain; the parent mapper's behavior isn't modified by this method. @@ -144,11 +144,11 @@ class MapperExtension(object): def init_failed(self, mapper, class_, oldinit, instance, args, kwargs): """Receive an instance when it's constructor has been called, and raised an exception. - + This method is only called during a userland construction of an object. It is not called when an object is loaded from the database. - + The return value is only significant within the ``MapperExtension`` chain; the parent mapper's behavior isn't modified by this method. @@ -166,10 +166,10 @@ class MapperExtension(object): object which contains mapped columns as keys. The returned object should also be a dictionary-like object which recognizes mapped columns as keys. - + If the ultimate return value is EXT_CONTINUE, the row is not translated. - + """ return EXT_CONTINUE @@ -302,7 +302,7 @@ class MapperExtension(object): The return value is only significant within the ``MapperExtension`` chain; the parent mapper's behavior isn't modified by this method. - + """ return EXT_CONTINUE @@ -319,7 +319,7 @@ class MapperExtension(object): This means that an instance being sent to before_update is *not* a guarantee that an UPDATE statement will be issued (although you can affect the outcome here). - + To detect if the column-based attributes on the object have net changes, and will therefore generate an UPDATE statement, use ``object_session(instance).is_modified(instance, @@ -344,7 +344,7 @@ class MapperExtension(object): The return value is only significant within the ``MapperExtension`` chain; the parent mapper's behavior isn't modified by this method. - + """ return EXT_CONTINUE @@ -377,17 +377,17 @@ class MapperExtension(object): class SessionExtension(object): """Base implementation for :class:`.Session` event hooks. - + .. note:: :class:`.SessionExtension` is deprecated. Please refer to :func:`.event.listen` as well as :class:`.SessionEvents`. - + Subclasses may be installed into a :class:`.Session` (or :func:`.sessionmaker`) using the ``extension`` keyword argument:: - + from sqlalchemy.orm.interfaces import SessionExtension - + class MySessionExtension(SessionExtension): def before_commit(self, session): print "before commit!" @@ -414,32 +414,32 @@ class SessionExtension(object): def before_commit(self, session): """Execute right before commit is called. - + Note that this may not be per-flush if a longer running transaction is ongoing.""" def after_commit(self, session): """Execute after a commit has occured. - + Note that this may not be per-flush if a longer running transaction is ongoing.""" def after_rollback(self, session): """Execute after a rollback has occured. - + Note that this may not be per-flush if a longer running transaction is ongoing.""" def before_flush( self, session, flush_context, instances): """Execute before flush process has started. - + `instances` is an optional list of objects which were passed to the ``flush()`` method. """ def after_flush(self, session, flush_context): """Execute after flush has completed, but before commit has been called. - + Note that the session's state is still in pre-flush, i.e. 'new', 'dirty', and 'deleted' lists still show pre-flush state as well as the history settings on instance attributes.""" @@ -447,7 +447,7 @@ class SessionExtension(object): def after_flush_postexec(self, session, flush_context): """Execute after flush has completed, and after the post-exec state occurs. - + This will be when the 'new', 'dirty', and 'deleted' lists are in their final state. An actual commit() may or may not have occured, depending on whether or not the flush started its own @@ -455,20 +455,20 @@ class SessionExtension(object): def after_begin( self, session, transaction, connection): """Execute after a transaction is begun on a connection - + `transaction` is the SessionTransaction. This method is called after an engine level transaction is begun on a connection. """ def after_attach(self, session, instance): """Execute after an instance is attached to a session. - + This is called after an add, delete or merge. """ def after_bulk_update( self, session, query, query_context, result): """Execute after a bulk update operation to the session. - + This is called after a session.query(...).update() - + `query` is the query object that this update operation was called on. `query_context` was the query context object. `result` is the result object returned from the bulk operation. @@ -476,9 +476,9 @@ class SessionExtension(object): def after_bulk_delete( self, session, query, query_context, result): """Execute after a bulk delete operation to the session. - + This is called after a session.query(...).delete() - + `query` is the query object that this delete operation was called on. `query_context` was the query context object. `result` is the result object returned from the bulk operation. @@ -492,7 +492,7 @@ class AttributeExtension(object): .. note:: :class:`.AttributeExtension` is deprecated. Please refer to :func:`.event.listen` as well as :class:`.AttributeEvents`. - + :class:`.AttributeExtension` is used to listen for set, remove, and append events on individual mapped attributes. It is established on an individual mapped attribute using @@ -502,16 +502,16 @@ class AttributeExtension(object): from sqlalchemy.orm.interfaces import AttributeExtension from sqlalchemy.orm import mapper, relationship, column_property - + class MyAttrExt(AttributeExtension): def append(self, state, value, initiator): print "append event !" return value - + def set(self, state, value, oldvalue, initiator): print "set event !" return value - + mapper(SomeClass, sometable, properties={ 'foo':column_property(sometable.c.foo, extension=MyAttrExt()), 'bar':relationship(Bar, extension=MyAttrExt()) @@ -523,10 +523,10 @@ class AttributeExtension(object): ``value`` parameter. The returned value is used as the effective value, and allows the extension to change what is ultimately persisted. - + AttributeExtension is assembled within the descriptors associated with a mapped class. - + """ active_history = True @@ -535,7 +535,7 @@ class AttributeExtension(object): Note that ``active_history`` can also be set directly via :func:`.column_property` and :func:`.relationship`. - + """ @classmethod @@ -549,7 +549,7 @@ class AttributeExtension(object): event.listen(self, 'set', listener.set, active_history=listener.active_history, raw=True, retval=True) - + def append(self, state, value, initiator): """Receive a collection append event. diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 06da99e07..e6166aa9e 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -6,7 +6,7 @@ """Descriptor proprerties are more "auxilliary" properties that exist as configurational elements, but don't participate -as actively in the load/persist ORM loop. +as actively in the load/persist ORM loop. """ @@ -20,23 +20,23 @@ properties = util.importlater('sqlalchemy.orm', 'properties') class DescriptorProperty(MapperProperty): """:class:`MapperProperty` which proxies access to a user-defined descriptor.""" - + doc = None - + def instrument_class(self, mapper): prop = self - + class _ProxyImpl(object): accepts_scalar_loader = False expire_missing = True def __init__(self, key): self.key = key - + if hasattr(prop, 'get_history'): def get_history(self, state, dict_, **kw): return prop.get_history(state, dict_, **kw) - + if self.descriptor is None: desc = getattr(mapper.class_, self.key, None) if mapper._is_userland_descriptor(desc): @@ -55,7 +55,7 @@ class DescriptorProperty(MapperProperty): fset=fset, fdel=fdel, ) - + proxy_attr = attributes.\ create_proxied_attribute(self.descriptor)\ ( @@ -68,10 +68,10 @@ class DescriptorProperty(MapperProperty): proxy_attr.property = self proxy_attr.impl = _ProxyImpl(self.key) mapper.class_manager.instrument_attribute(self.key, proxy_attr) - + class CompositeProperty(DescriptorProperty): - + def __init__(self, class_, *columns, **kwargs): self.columns = columns self.composite_class = class_ @@ -84,32 +84,32 @@ class CompositeProperty(DescriptorProperty): def instrument_class(self, mapper): super(CompositeProperty, self).instrument_class(mapper) self._setup_event_handlers() - + def do_init(self): """Initialization which occurs after the :class:`.CompositeProperty` has been associated with its parent mapper. - + """ self._setup_arguments_on_columns() - + def _create_descriptor(self): """Create the Python descriptor that will serve as the access point on instances of the mapped class. - + """ def fget(instance): dict_ = attributes.instance_dict(instance) - + # key not present, assume the columns aren't # loaded. The load events will establish # the item. if self.key not in dict_: for key in self._attribute_keys: getattr(instance, key) - + return dict_.get(self.key, None) - + def fset(instance, value): dict_ = attributes.instance_dict(instance) state = attributes.instance_state(instance) @@ -126,7 +126,7 @@ class CompositeProperty(DescriptorProperty): self._attribute_keys, value.__composite_values__()): setattr(instance, key, value) - + def fdel(instance): state = attributes.instance_state(instance) dict_ = attributes.instance_dict(instance) @@ -135,13 +135,13 @@ class CompositeProperty(DescriptorProperty): attr.dispatch.remove(state, previous, attr.impl) for key in self._attribute_keys: setattr(instance, key, None) - + self.descriptor = property(fget, fset, fdel) - + def _setup_arguments_on_columns(self): """Propagate configuration arguments made on this composite to the target columns, for those that apply. - + """ for col in self.columns: prop = self.parent._columntoproperty[col] @@ -153,35 +153,35 @@ class CompositeProperty(DescriptorProperty): def _setup_event_handlers(self): """Establish events that populate/expire the composite attribute.""" - + def load_handler(state, *args): dict_ = state.dict - + if self.key in dict_: return - + # if column elements aren't loaded, skip. # __get__() will initiate a load for those # columns for k in self._attribute_keys: if k not in dict_: return - + dict_[self.key] = self.composite_class( *[state.dict[key] for key in self._attribute_keys] ) - + def expire_handler(state, keys): if keys is None or set(self._attribute_keys).intersection(keys): state.dict.pop(self.key, None) - + def insert_update_handler(mapper, connection, state): state.dict[self.key] = self.composite_class( *[state.dict.get(key, None) for key in self._attribute_keys] ) - + event.listen(self.parent, 'after_insert', insert_update_handler, raw=True) event.listen(self.parent, 'after_update', @@ -189,35 +189,35 @@ class CompositeProperty(DescriptorProperty): event.listen(self.parent, 'load', load_handler, raw=True) event.listen(self.parent, 'refresh', load_handler, raw=True) event.listen(self.parent, "expire", expire_handler, raw=True) - + # TODO: need a deserialize hook here - + @util.memoized_property def _attribute_keys(self): return [ self.parent._columntoproperty[col].key for col in self.columns ] - + def get_history(self, state, dict_, **kw): """Provided for userland code that uses attributes.get_history().""" - + added = [] deleted = [] - + has_history = False for col in self.columns: key = self.parent._columntoproperty[col].key hist = state.manager[key].impl.get_history(state, dict_) if hist.has_changes(): has_history = True - + added.extend(hist.non_deleted()) if hist.deleted: deleted.extend(hist.deleted) else: deleted.append(None) - + if has_history: return attributes.History( [self.composite_class(*added)], @@ -236,7 +236,7 @@ class CompositeProperty(DescriptorProperty): def __init__(self, prop, adapter=None): self.prop = prop self.adapter = adapter - + def __clause_element__(self): if self.adapter: # TODO: test coverage for adapted composite comparison @@ -244,9 +244,9 @@ class CompositeProperty(DescriptorProperty): *[self.adapter(x) for x in self.prop.columns]) else: return expression.ClauseList(*self.prop.columns) - + __hash__ = None - + def __eq__(self, other): if other is None: values = [None] * len(self.prop.columns) @@ -254,7 +254,7 @@ class CompositeProperty(DescriptorProperty): values = other.__composite_values__() return sql.and_( *[a==b for a, b in zip(self.prop.columns, values)]) - + def __ne__(self, other): return sql.not_(self.__eq__(other)) @@ -280,14 +280,14 @@ class ConcreteInheritedProperty(DescriptorProperty): def _comparator_factory(self, mapper): comparator_callable = None - + for m in self.parent.iterate_to_root(): p = m._props[self.key] if not isinstance(p, ConcreteInheritedProperty): comparator_callable = p.comparator_factory break return comparator_callable - + def __init__(self): def warn(): raise AttributeError("Concrete %s does not implement " @@ -305,8 +305,8 @@ class ConcreteInheritedProperty(DescriptorProperty): return self.descriptor warn() self.descriptor = NoninheritedConcreteProp() - - + + class SynonymProperty(DescriptorProperty): def __init__(self, name, map_column=None, @@ -317,16 +317,16 @@ class SynonymProperty(DescriptorProperty): self.descriptor = descriptor self.comparator_factory = comparator_factory self.doc = doc or (descriptor and descriptor.__doc__) or None - + util.set_creation_order(self) - + # TODO: when initialized, check _proxied_property, # emit a warning if its not a column-based property - + @util.memoized_property def _proxied_property(self): return getattr(self.parent.class_, self.name).property - + def _comparator_factory(self, mapper): prop = self._proxied_property @@ -361,9 +361,9 @@ class SynonymProperty(DescriptorProperty): init=init, setparent=True) p._mapped_by_synonym = self.key - + self.parent = parent - + class ComparableProperty(DescriptorProperty): """Instruments a Python property for use in query expressions.""" diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 7d12900cc..8dbdd8ffe 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -41,7 +41,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl): uses_objects = True accepts_scalar_loader = False supports_population = False - + def __init__(self, class_, key, typecallable, dispatch, target_mapper, order_by, query_class=None, **kw): @@ -131,12 +131,12 @@ class DynamicAttributeImpl(attributes.AttributeImpl): def set_committed_value(self, state, dict_, value): raise NotImplementedError("Dynamic attributes don't support " "collection population.") - + def get_history(self, state, dict_, passive=False): c = self._get_collection_history(state, passive) return attributes.History(c.added_items, c.unchanged_items, c.deleted_items) - + def get_all_pending(self, state, dict_): c = self._get_collection_history(state, True) return [ @@ -144,7 +144,7 @@ class DynamicAttributeImpl(attributes.AttributeImpl): for x in 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] @@ -265,10 +265,10 @@ class AppenderMixin(object): query = self.query_class(self.attr.target_mapper, session=sess) else: query = sess.query(self.attr.target_mapper) - + query._criterion = self._criterion query._order_by = self._order_by - + return query def append(self, item): @@ -307,4 +307,4 @@ class CollectionHistory(object): self.deleted_items = [] self.added_items = [] self.unchanged_items = [] - + diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 5fe795db2..761ba315d 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -12,20 +12,20 @@ import inspect class InstrumentationEvents(event.Events): """Events related to class instrumentation events. - + The listeners here support being established against any new style class, that is any object that is a subclass of 'type'. Events will then be fired off for events - against that class as well as all subclasses. + against that class as well as all subclasses. 'type' itself is also accepted as a target in which case the events fire for all classes. - + """ - + @classmethod def _accept_with(cls, target): from sqlalchemy.orm.instrumentation import instrumentation_registry - + if isinstance(target, type): return instrumentation_registry else: @@ -41,36 +41,36 @@ class InstrumentationEvents(event.Events): def class_instrument(self, cls): """Called after the given class is instrumented. - + To get at the :class:`.ClassManager`, use :func:`.manager_of_class`. - + """ def class_uninstrument(self, cls): """Called before the given class is uninstrumented. - + To get at the :class:`.ClassManager`, use :func:`.manager_of_class`. - + """ - - + + def attribute_instrument(self, cls, key, inst): """Called when an attribute is instrumented.""" class InstanceEvents(event.Events): """Define events specific to object lifecycle. - + Instance-level don't automatically propagate their associations to subclasses. - + """ @classmethod def _accept_with(cls, target): from sqlalchemy.orm.instrumentation import ClassManager, manager_of_class from sqlalchemy.orm import Mapper, mapper - + if isinstance(target, ClassManager): return target elif isinstance(target, Mapper): @@ -85,7 +85,7 @@ class InstanceEvents(event.Events): if manager: return manager return None - + @classmethod def _listen(cls, target, identifier, fn, raw=False, propagate=False): if not raw: @@ -98,7 +98,7 @@ class InstanceEvents(event.Events): if propagate: for mgr in target.subclass_managers(True): event.Events._listen(mgr, identifier, fn, True) - + @classmethod def _remove(cls, identifier, target, fn): raise NotImplementedError("Removal of instance events not yet implemented") @@ -107,26 +107,26 @@ class InstanceEvents(event.Events): """Called when the first instance of a particular mapping is called. """ - + def init(self, target, args, kwargs): """Receive an instance when it's constructor is called. - + This method is only called during a userland construction of an object. It is not called when an object is loaded from the database. """ - + def init_failure(self, target, args, kwargs): """Receive an instance when it's constructor has been called, and raised an exception. - + This method is only called during a userland construction of an object. It is not called when an object is loaded from the database. """ - + def load(self, target, context): """Receive an object instance after it has been created via ``__new__``, and after initial attribute population has @@ -153,7 +153,7 @@ class InstanceEvents(event.Events): def refresh(self, target, context, attrs): """Receive an object instance after one or more attributes have been refreshed from a query. - + :param target: the mapped instance. If the event is configured with ``raw=True``, this will instead be the :class:`.InstanceState` state-management @@ -163,13 +163,13 @@ class InstanceEvents(event.Events): :param attrs: iterable collection of attribute names which were populated, or None if all column-mapped, non-deferred attributes were populated. - + """ - + def expire(self, target, attrs): """Receive an object instance after its attributes or some subset have been expired. - + 'keys' is a list of attribute names. If None, the entire state was expired. @@ -180,27 +180,27 @@ class InstanceEvents(event.Events): :param attrs: iterable collection of attribute names which were expired, or None if all attributes were expired. - + """ - + def resurrect(self, target): """Receive an object instance as it is 'resurrected' from garbage collection, which occurs when a "dirty" state falls out of scope. - + :param target: the mapped instance. If the event is configured with ``raw=True``, this will instead be the :class:`.InstanceState` state-management object associated with the instance. - + """ - + class MapperEvents(event.Events): """Define events specific to mappings. e.g.:: - + from sqlalchemy import event def my_before_insert_listener(mapper, connection, target): @@ -209,7 +209,7 @@ class MapperEvents(event.Events): target.calculated_value = connection.scalar( "select my_special_function(%d)" % target.special_number) - + # associate the listener function with SomeMappedClass, # to execute during the "before_insert" hook event.listen(SomeMappedClass, 'before_insert', my_before_insert_listener) @@ -221,13 +221,13 @@ class MapperEvents(event.Events): for global event reception:: from sqlalchemy.orm import mapper - + def some_listener(mapper, connection, target): log.debug("Instance %s being inserted" % target) - + # attach to all mappers event.listen(mapper, 'before_insert', some_listener) - + Mapper events provide hooks into critical sections of the mapper, including those related to object instrumentation, object loading, and object persistence. In particular, the @@ -240,10 +240,10 @@ class MapperEvents(event.Events): :meth:`.SessionEvents.after_flush` methods as more flexible and user-friendly hooks in which to apply additional database state during a flush. - + When using :class:`.MapperEvents`, several modifiers are available to the :func:`.event.listen` function. - + :param propagate=False: When True, the event listener should be applied to all inheriting mappers as well as the mapper which is the target of this listener. @@ -256,7 +256,7 @@ class MapperEvents(event.Events): control subsequent event propagation, or to otherwise alter the operation in progress by the mapper. Possible return values are: - + * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event processing normally. * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent @@ -264,7 +264,7 @@ class MapperEvents(event.Events): * other values - the return value specified by specific listeners, such as :meth:`~.MapperEvents.translate_row` or :meth:`~.MapperEvents.create_instance`. - + """ @classmethod @@ -279,7 +279,7 @@ class MapperEvents(event.Events): return class_mapper(target) else: return target - + @classmethod def _listen(cls, target, identifier, fn, raw=False, retval=False, propagate=False): @@ -292,7 +292,7 @@ class MapperEvents(event.Events): target_index = inspect.getargspec(meth)[0].index('target') - 1 except ValueError: target_index = None - + wrapped_fn = fn def wrap(*arg, **kw): if not raw and target_index is not None: @@ -304,42 +304,42 @@ class MapperEvents(event.Events): else: return wrapped_fn(*arg, **kw) fn = wrap - + if propagate: for mapper in target.self_and_descendants: event.Events._listen(mapper, identifier, fn, propagate=True) else: event.Events._listen(target, identifier, fn) - + def instrument_class(self, mapper, class_): """Receive a class when the mapper is first constructed, before instrumentation is applied to the mapped class. - + This event is the earliest phase of mapper construction. Most attributes of the mapper are not yet initialized. - + This listener can generally only be applied to the :class:`.Mapper` class overall. - + :param mapper: the :class:`.Mapper` which is the target of this event. :param class\_: the mapped class. - + """ - + def mapper_configured(self, mapper, class_): """Called when the mapper for the class is fully configured. This event is the latest phase of mapper construction. The mapper should be in its final state. - + :param mapper: the :class:`.Mapper` which is the target of this event. :param class\_: the mapped class. - + """ # TODO: need coverage for this event - + def translate_row(self, mapper, context, row): """Perform pre-processing on the given result row and return a new row instance. @@ -352,7 +352,7 @@ class MapperEvents(event.Events): object which contains mapped columns as keys. The returned object should also be a dictionary-like object which recognizes mapped columns as keys. - + :param mapper: the :class:`.Mapper` which is the target of this event. :param context: the :class:`.QueryContext`, which includes @@ -364,8 +364,8 @@ class MapperEvents(event.Events): :return: When configured with ``retval=True``, the function should return a dictionary-like row object, or ``EXT_CONTINUE``, indicating the original row should be used. - - + + """ def create_instance(self, mapper, context, row, class_): @@ -396,10 +396,10 @@ class MapperEvents(event.Events): result, **flags): """Receive an object instance before that instance is appended to a result list. - + This is a rarely used hook which can be used to alter the construction of a result list returned by :class:`.Query`. - + :param mapper: the :class:`.Mapper` which is the target of this event. :param context: the :class:`.QueryContext`, which includes @@ -435,7 +435,7 @@ class MapperEvents(event.Events): unloaded attributes to be populated. The method may be called many times for a single instance, as multiple result rows are used to populate eagerly loaded collections. - + Most usages of this hook are obsolete. For a generic "object has been newly created from a row" hook, use :meth:`.InstanceEvents.load`. @@ -462,12 +462,12 @@ class MapperEvents(event.Events): def before_insert(self, mapper, connection, target): """Receive an object instance before an INSERT statement is emitted corresponding to that instance. - + This event is used to modify local, non-object related attributes on the instance before an INSERT occurs, as well as to emit additional SQL statements on the given - connection. - + connection. + The event is often called for a batch of objects of the same class before their INSERT statements are emitted at once in a later step. In the extremely rare case that @@ -476,7 +476,7 @@ class MapperEvents(event.Events): batches of instances to be broken up into individual (and more poorly performing) event->persist->event steps. - + Handlers should **not** modify any attributes which are mapped by :func:`.relationship`, nor should they attempt to make any modifications to the :class:`.Session` in @@ -502,11 +502,11 @@ class MapperEvents(event.Events): def after_insert(self, mapper, connection, target): """Receive an object instance after an INSERT statement is emitted corresponding to that instance. - + This event is used to modify in-Python-only state on the instance after an INSERT occurs, as well as to emit additional SQL statements on the given - connection. + connection. The event is often called for a batch of objects of the same class after their INSERT statements have been @@ -528,7 +528,7 @@ class MapperEvents(event.Events): instead be the :class:`.InstanceState` state-management object associated with the instance. :return: No return value is supported by this event. - + """ def before_update(self, mapper, connection, target): @@ -538,7 +538,7 @@ class MapperEvents(event.Events): This event is used to modify local, non-object related attributes on the instance before an UPDATE occurs, as well as to emit additional SQL statements on the given - connection. + connection. This method is called for all instances that are marked as "dirty", *even those which have no net changes @@ -553,7 +553,7 @@ class MapperEvents(event.Events): issued, although you can affect the outcome here by modifying attributes so that a net change in value does exist. - + To detect if the column-based attributes on the object have net changes, and will therefore generate an UPDATE statement, use ``object_session(instance).is_modified(instance, @@ -567,7 +567,7 @@ class MapperEvents(event.Events): batches of instances to be broken up into individual (and more poorly performing) event->persist->event steps. - + Handlers should **not** modify any attributes which are mapped by :func:`.relationship`, nor should they attempt to make any modifications to the :class:`.Session` in @@ -596,7 +596,7 @@ class MapperEvents(event.Events): This event is used to modify in-Python-only state on the instance after an UPDATE occurs, as well as to emit additional SQL statements on the given - connection. + connection. This method is called for all instances that are marked as "dirty", *even those which have no net changes @@ -610,7 +610,7 @@ class MapperEvents(event.Events): being sent to :meth:`~.MapperEvents.after_update` is *not* a guarantee that an UPDATE statement has been issued. - + To detect if the column-based attributes on the object have net changes, and therefore resulted in an UPDATE statement, use ``object_session(instance).is_modified(instance, @@ -624,7 +624,7 @@ class MapperEvents(event.Events): batches of instances to be broken up into individual (and more poorly performing) event->persist->event steps. - + :param mapper: the :class:`.Mapper` which is the target of this event. :param connection: the :class:`.Connection` being used to @@ -636,21 +636,21 @@ class MapperEvents(event.Events): instead be the :class:`.InstanceState` state-management object associated with the instance. :return: No return value is supported by this event. - + """ def before_delete(self, mapper, connection, target): """Receive an object instance before a DELETE statement is emitted corresponding to that instance. - + This event is used to emit additional SQL statements on the given connection as well as to perform application specific bookkeeping related to a deletion event. - + The event is often called for a batch of objects of the same class before their DELETE statements are emitted at once in a later step. - + Handlers should **not** modify any attributes which are mapped by :func:`.relationship`, nor should they attempt to make any modifications to the :class:`.Session` in @@ -670,17 +670,17 @@ class MapperEvents(event.Events): instead be the :class:`.InstanceState` state-management object associated with the instance. :return: No return value is supported by this event. - + """ def after_delete(self, mapper, connection, target): """Receive an object instance after a DELETE statement has been emitted corresponding to that instance. - + This event is used to emit additional SQL statements on the given connection as well as to perform application specific bookkeeping related to a deletion event. - + The event is often called for a batch of objects of the same class after their DELETE statements have been emitted at once in a previous step. @@ -696,36 +696,36 @@ class MapperEvents(event.Events): instead be the :class:`.InstanceState` state-management object associated with the instance. :return: No return value is supported by this event. - + """ @classmethod def _remove(cls, identifier, target, fn): raise NotImplementedError("Removal of mapper events not yet implemented") - + class SessionEvents(event.Events): """Define events specific to :class:`.Session` lifecycle. - + e.g.:: - + from sqlalchemy import event from sqlalchemy.orm import sessionmaker - + class my_before_commit(session): print "before commit!" - + Session = sessionmaker() - + event.listen(Session, "before_commit", my_before_commit) - + The :func:`~.event.listen` function will accept :class:`.Session` objects as well as the return result of :func:`.sessionmaker` and :func:`.scoped_session`. - + Additionally, it accepts the :class:`.Session` class which will apply listeners to all :class:`.Session` instances globally. - + """ @classmethod @@ -748,39 +748,39 @@ class SessionEvents(event.Events): return target else: return None - + @classmethod def _remove(cls, identifier, target, fn): raise NotImplementedError("Removal of session events not yet implemented") def before_commit(self, session): """Execute before commit is called. - + Note that this may not be per-flush if a longer running transaction is ongoing.""" def after_commit(self, session): """Execute after a commit has occured. - + Note that this may not be per-flush if a longer running transaction is ongoing.""" def after_rollback(self, session): """Execute after a rollback has occured. - + Note that this may not be per-flush if a longer running transaction is ongoing.""" def before_flush( self, session, flush_context, instances): """Execute before flush process has started. - + `instances` is an optional list of objects which were passed to the ``flush()`` method. """ def after_flush(self, session, flush_context): """Execute after flush has completed, but before commit has been called. - + Note that the session's state is still in pre-flush, i.e. 'new', 'dirty', and 'deleted' lists still show pre-flush state as well as the history settings on instance attributes.""" @@ -788,7 +788,7 @@ class SessionEvents(event.Events): def after_flush_postexec(self, session, flush_context): """Execute after flush has completed, and after the post-exec state occurs. - + This will be when the 'new', 'dirty', and 'deleted' lists are in their final state. An actual commit() may or may not have occured, depending on whether or not the flush started its own @@ -796,20 +796,20 @@ class SessionEvents(event.Events): def after_begin( self, session, transaction, connection): """Execute after a transaction is begun on a connection - + `transaction` is the SessionTransaction. This method is called after an engine level transaction is begun on a connection. """ def after_attach(self, session, instance): """Execute after an instance is attached to a session. - + This is called after an add, delete or merge. """ def after_bulk_update( self, session, query, query_context, result): """Execute after a bulk update operation to the session. - + This is called after a session.query(...).update() - + `query` is the query object that this update operation was called on. `query_context` was the query context object. `result` is the result object returned from the bulk operation. @@ -817,9 +817,9 @@ class SessionEvents(event.Events): def after_bulk_delete( self, session, query, query_context, result): """Execute after a bulk delete operation to the session. - + This is called after a session.query(...).delete() - + `query` is the query object that this delete operation was called on. `query_context` was the query context object. `result` is the result object returned from the bulk operation. @@ -828,37 +828,37 @@ class SessionEvents(event.Events): class AttributeEvents(event.Events): """Define events for object attributes. - + These are typically defined on the class-bound descriptor for the target class. e.g.:: - + from sqlalchemy import event - + def my_append_listener(target, value, initiator): print "received append event for target: %s" % target - + event.listen(MyClass.collection, 'append', my_append_listener) - + Listeners have the option to return a possibly modified version of the value, when the ``retval=True`` flag is passed to :func:`~.event.listen`:: - + def validate_phone(target, value, oldvalue, initiator): "Strip non-numeric characters from a phone number" - + return re.sub(r'(?![0-9])', '', value) - + # setup listener on UserContact.phone attribute, instructing # it to use the return value listen(UserContact.phone, 'set', validate_phone, retval=True) - + A validation function like the above can also raise an exception such as :class:`ValueError` to halt the operation. - + Several modifiers are available to the :func:`~.event.listen` function. - + :param active_history=False: When True, indicates that the "set" event would like to receive the "old" value being replaced unconditionally, even if this requires firing off @@ -879,8 +879,8 @@ class AttributeEvents(event.Events): listening must return the "value" argument from the function. This gives the listening function the opportunity to change the value that is ultimately used for a "set" - or "append" event. - + or "append" event. + """ @classmethod @@ -891,17 +891,17 @@ class AttributeEvents(event.Events): return getattr(target.parent.class_, target.key) else: return target - + @classmethod def _listen(cls, target, identifier, fn, active_history=False, raw=False, retval=False, propagate=False): if active_history: target.dispatch._active_history = True - + # TODO: for removal, need to package the identity # of the wrapper with the original function. - + if not raw or not retval: orig_fn = fn def wrap(target, value, *arg): @@ -913,21 +913,21 @@ class AttributeEvents(event.Events): else: return orig_fn(target, value, *arg) fn = wrap - + event.Events._listen(target, identifier, fn, propagate) - + if propagate: from sqlalchemy.orm.instrumentation import manager_of_class - + manager = manager_of_class(target.class_) - + for mgr in manager.subclass_managers(True): event.Events._listen(mgr[target.key], identifier, fn, True) - + @classmethod def _remove(cls, identifier, target, fn): raise NotImplementedError("Removal of attribute events not yet implemented") - + def append(self, target, value, initiator): """Receive a collection append event. @@ -942,7 +942,7 @@ class AttributeEvents(event.Events): which initiated this event. :return: if the event was registered with ``retval=True``, the given value, or a new effective value, should be returned. - + """ def remove(self, target, value, initiator): diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py index a180f3725..b86e5c7c3 100644 --- a/lib/sqlalchemy/orm/exc.py +++ b/lib/sqlalchemy/orm/exc.py @@ -14,22 +14,22 @@ NO_STATE = (AttributeError, KeyError) class StaleDataError(sa.exc.SQLAlchemyError): """An operation encountered database state that is unaccounted for. - + Two conditions cause this to happen: - + * A flush may have attempted to update or delete rows and an unexpected number of rows were matched during the UPDATE or DELETE statement. Note that when version_id_col is used, rows in UPDATE or DELETE statements are also matched against the current known version identifier. - + * A mapped object with version_id_col was refreshed, and the version number coming back from the database does not match that of the object itself. - + """ - + ConcurrentModificationError = StaleDataError @@ -43,7 +43,7 @@ class UnmappedError(sa.exc.InvalidRequestError): class DetachedInstanceError(sa.exc.SQLAlchemyError): """An attempt to access unloaded attributes on a mapped instance that is detached.""" - + class UnmappedInstanceError(UnmappedError): """An mapping operation was requested for an unknown instance.""" diff --git a/lib/sqlalchemy/orm/identity.py b/lib/sqlalchemy/orm/identity.py index 83687d682..b3a7f8bc3 100644 --- a/lib/sqlalchemy/orm/identity.py +++ b/lib/sqlalchemy/orm/identity.py @@ -15,30 +15,30 @@ class IdentityMap(dict): self._mutable_attrs = set() self._modified = set() self._wr = weakref.ref(self) - + def replace(self, state): raise NotImplementedError() - + def add(self, state): raise NotImplementedError() - + def remove(self, state): raise NotImplementedError() - + def update(self, dict): raise NotImplementedError("IdentityMap uses add() to insert data") - + def clear(self): raise NotImplementedError("IdentityMap uses remove() to remove data") - + def _manage_incoming_state(self, state): state._instance_dict = self._wr - + if state.modified: - self._modified.add(state) + self._modified.add(state) if state.manager.mutable_attributes: self._mutable_attrs.add(state) - + def _manage_removed_state(self, state): del state._instance_dict self._mutable_attrs.discard(state) @@ -50,7 +50,7 @@ class IdentityMap(dict): def check_modified(self): """return True if any InstanceStates present have been marked as 'modified'.""" - + if self._modified: return True else: @@ -58,10 +58,10 @@ class IdentityMap(dict): if state.modified: return True return False - + def has_key(self, key): return key in self - + def popitem(self): raise NotImplementedError("IdentityMap uses remove() to remove data") @@ -79,7 +79,7 @@ class IdentityMap(dict): def __delitem__(self, key): raise NotImplementedError("IdentityMap uses remove() to remove data") - + class WeakInstanceDict(IdentityMap): def __init__(self): IdentityMap.__init__(self) @@ -107,10 +107,10 @@ class WeakInstanceDict(IdentityMap): return False else: return o is not None - + def contains_state(self, state): return dict.get(self, state.key) is state - + def replace(self, state): if dict.__contains__(self, state.key): existing = dict.__getitem__(self, state.key) @@ -118,7 +118,7 @@ class WeakInstanceDict(IdentityMap): self._manage_removed_state(existing) else: return - + dict.__setitem__(self, state.key, state) self._manage_incoming_state(state) @@ -146,7 +146,7 @@ class WeakInstanceDict(IdentityMap): def remove_key(self, key): state = dict.__getitem__(self, key) self.remove(state) - + def remove(self, state): self._remove_mutex.acquire() try: @@ -156,14 +156,14 @@ class WeakInstanceDict(IdentityMap): "identity map" % state) finally: self._remove_mutex.release() - + self._manage_removed_state(state) - + def discard(self, state): if self.contains_state(state): dict.__delitem__(self, state.key) self._manage_removed_state(state) - + def get(self, key, default=None): if not dict.__contains__(self, key): return default @@ -178,7 +178,7 @@ class WeakInstanceDict(IdentityMap): def items(self): # Py2K return list(self.iteritems()) - + def iteritems(self): # end Py2K self._remove_mutex.acquire() @@ -192,7 +192,7 @@ class WeakInstanceDict(IdentityMap): return iter(result) finally: self._remove_mutex.release() - + def values(self): # Py2K return list(self.itervalues()) @@ -210,29 +210,29 @@ class WeakInstanceDict(IdentityMap): return iter(result) finally: self._remove_mutex.release() - + def all_states(self): self._remove_mutex.acquire() try: # Py3K # return list(dict.values(self)) - + # Py2K return dict.values(self) # end Py2K finally: self._remove_mutex.release() - + def prune(self): return 0 - + class StrongInstanceDict(IdentityMap): def all_states(self): return [attributes.instance_state(o) for o in self.itervalues()] - + def contains_state(self, state): return state.key in self and attributes.instance_state(self[state.key]) is state - + def replace(self, state): if dict.__contains__(self, state.key): existing = dict.__getitem__(self, state.key) @@ -255,26 +255,26 @@ class StrongInstanceDict(IdentityMap): else: dict.__setitem__(self, state.key, state.obj()) self._manage_incoming_state(state) - + def remove(self, state): if attributes.instance_state(dict.pop(self, state.key)) \ is not state: raise AssertionError('State %s is not present in this ' 'identity map' % state) self._manage_removed_state(state) - + def discard(self, state): if self.contains_state(state): dict.__delitem__(self, state.key) self._manage_removed_state(state) - + def remove_key(self, key): state = attributes.instance_state(dict.__getitem__(self, key)) self.remove(state) def prune(self): """prune unreferenced, non-dirty states.""" - + ref_count = len(self) dirty = [s.obj() for s in self.all_states() if s.modified] @@ -286,4 +286,4 @@ class StrongInstanceDict(IdentityMap): dict.update(self, keepers) self.modified = bool(dirty) return ref_count - len(self) - + diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py index 3ba9190c0..aa051490c 100644 --- a/lib/sqlalchemy/orm/instrumentation.py +++ b/lib/sqlalchemy/orm/instrumentation.py @@ -78,9 +78,9 @@ class ClassManager(dict): STATE_ATTR = '_sa_instance_state' deferred_scalar_loader = None - + original_init = object.__init__ - + def __init__(self, class_): self.class_ = class_ self.factory = None # where we came from, for inheritance bookkeeping @@ -101,30 +101,30 @@ class ClassManager(dict): self.manage() self._instrument_init() - + dispatch = event.dispatcher(events.InstanceEvents) - + @property def is_mapped(self): return 'mapper' in self.__dict__ - + @util.memoized_property def mapper(self): raise exc.UnmappedClassError(self.class_) - + def _attr_has_impl(self, key): """Return True if the given attribute is fully initialized. - + i.e. has an impl. """ - + return key in self and self[key].impl is not None - + def _configure_create_arguments(self, _source=None, deferred_scalar_loader=None): """Accept extra **kw arguments passed to create_manager_for_cls. - + The current contract of ClassManager and other managers is that they take a single "cls" argument in their constructor (as per test/orm/instrumentation.py InstrumentationCollisionTest). This @@ -133,30 +133,30 @@ class ClassManager(dict): ClassManager-like instances. So create_manager_for_cls sends in ClassManager-specific arguments via this method once the non-proxied ClassManager is available. - + """ if _source: deferred_scalar_loader = _source.deferred_scalar_loader if deferred_scalar_loader: self.deferred_scalar_loader = deferred_scalar_loader - + def _subclass_manager(self, cls): """Create a new ClassManager for a subclass of this ClassManager's class. - + This is called automatically when attributes are instrumented so that the attributes can be propagated to subclasses against their own class-local manager, without the need for mappers etc. to have already pre-configured managers for the full class hierarchy. Mappers can post-configure the auto-generated ClassManager when needed. - + """ manager = manager_of_class(cls) if manager is None: manager = _create_manager_for_cls(cls, _source=self) return manager - + def _instrument_init(self): # TODO: self.class_.__init__ is often the already-instrumented # __init__ from an instrumented superclass. We still need to make @@ -166,12 +166,12 @@ class ClassManager(dict): self.original_init = self.class_.__init__ self.new_init = _generate_init(self.class_, self) self.install_member('__init__', self.new_init) - + def _uninstrument_init(self): if self.new_init: self.uninstall_member('__init__') self.new_init = None - + @util.memoized_property def _state_constructor(self): self.dispatch.first_init(self, self.class_) @@ -179,15 +179,15 @@ class ClassManager(dict): return state.MutableAttrInstanceState else: return state.InstanceState - + def manage(self): """Mark this instance as the manager for its class.""" - + setattr(self.class_, self.MANAGER_ATTR, self) def dispose(self): """Dissasociate this manager from its class.""" - + delattr(self.class_, self.MANAGER_ATTR) def manager_getter(self): @@ -201,7 +201,7 @@ class ClassManager(dict): self.local_attrs[key] = inst self.install_descriptor(key, inst) self[key] = inst - + for cls in self.class_.__subclasses__(): manager = self._subclass_manager(cls) manager.instrument_attribute(key, inst, True) @@ -214,11 +214,11 @@ class ClassManager(dict): if recursive: for m in mgr.subclass_managers(True): yield m - + def post_configure_attribute(self, key): instrumentation_registry.dispatch.\ attribute_instrument(self.class_, key, self[key]) - + def uninstrument_attribute(self, key, propagated=False): if key not in self: return @@ -238,12 +238,12 @@ class ClassManager(dict): def unregister(self): """remove all instrumentation established by this ClassManager.""" - + self._uninstrument_init() self.mapper = self.dispatch = None self.info.clear() - + for key in list(self): if key in self.local_attrs: self.uninstrument_attribute(key) @@ -304,15 +304,15 @@ class ClassManager(dict): def setup_instance(self, instance, state=None): setattr(instance, self.STATE_ATTR, state or self._state_constructor(instance, self)) - + def teardown_instance(self, instance): delattr(instance, self.STATE_ATTR) - + def _new_state_if_none(self, instance): """Install a default InstanceState if none is present. A private convenience method used by the __init__ decorator. - + """ if hasattr(instance, self.STATE_ATTR): return False @@ -329,7 +329,7 @@ class ClassManager(dict): state = self._state_constructor(instance, self) setattr(instance, self.STATE_ATTR, state) return state - + def state_getter(self): """Return a (instance) -> InstanceState callable. @@ -339,13 +339,13 @@ class ClassManager(dict): """ return attrgetter(self.STATE_ATTR) - + def dict_getter(self): return attrgetter('__dict__') - + def has_state(self, instance): return hasattr(instance, self.STATE_ATTR) - + def has_parent(self, state, key, optimistic=False): """TODO""" return self.get_impl(key).hasparent(state, optimistic=optimistic) @@ -365,7 +365,7 @@ class _ClassInstrumentationAdapter(ClassManager): self._adapted = override self._get_state = self._adapted.state_getter(class_) self._get_dict = self._adapted.dict_getter(class_) - + ClassManager.__init__(self, class_, **kw) def manage(self): @@ -427,10 +427,10 @@ class _ClassInstrumentationAdapter(ClassManager): def setup_instance(self, instance, state=None): self._adapted.initialize_instance_dict(self.class_, instance) - + if state is None: state = self._state_constructor(instance, self) - + # the given instance is assumed to have no state self._adapted.install_state(self.class_, instance, state) return state @@ -445,7 +445,7 @@ class _ClassInstrumentationAdapter(ClassManager): return False else: return True - + def state_getter(self): return self._get_state @@ -454,7 +454,7 @@ class _ClassInstrumentationAdapter(ClassManager): def register_class(class_, **kw): """Register class instrumentation. - + Returns the existing or newly created class manager. """ @@ -462,31 +462,31 @@ def register_class(class_, **kw): if manager is None: manager = _create_manager_for_cls(class_, **kw) return manager - + def unregister_class(class_): """Unregister class instrumentation.""" - + instrumentation_registry.unregister(class_) def is_instrumented(instance, key): """Return True if the given attribute on the given instance is instrumented by the attributes package. - + This function may be used regardless of instrumentation applied directly to the class, i.e. no descriptors are required. - + """ return manager_of_class(instance.__class__).\ is_instrumented(key, search=True) class InstrumentationRegistry(object): """Private instrumentation registration singleton. - + All classes are routed through this registry when first instrumented, however the InstrumentationRegistry is not actually needed unless custom ClassManagers are in use. - + """ _manager_finders = weakref.WeakKeyDictionary() @@ -518,23 +518,23 @@ class InstrumentationRegistry(object): manager = factory(class_) if not isinstance(manager, ClassManager): manager = _ClassInstrumentationAdapter(class_, manager) - + if factory != ClassManager and not self._extended: # somebody invoked a custom ClassManager. # reinstall global "getter" functions with the more # expensive ones. self._extended = True _install_lookup_strategy(self) - + manager._configure_create_arguments(**kw) manager.factory = factory self._manager_finders[class_] = manager.manager_getter() self._state_finders[class_] = manager.state_getter() self._dict_finders[class_] = manager.dict_getter() - + self.dispatch.class_instrument(class_) - + return manager def _collect_management_factories_for(self, cls): @@ -597,7 +597,7 @@ class InstrumentationRegistry(object): except KeyError: raise AttributeError("%r is not instrumented" % instance.__class__) - + def unregister(self, class_): if class_ in self._manager_finders: manager = self.manager_of_class(class_) @@ -609,7 +609,7 @@ class InstrumentationRegistry(object): del self._dict_finders[class_] if ClassManager.MANAGER_ATTR in class_.__dict__: delattr(class_, ClassManager.MANAGER_ATTR) - + instrumentation_registry = InstrumentationRegistry() @@ -618,10 +618,10 @@ def _install_lookup_strategy(implementation): with either faster or more comprehensive implementations, based on whether or not extended class instrumentation has been detected. - + This function is called only by InstrumentationRegistry() and unit tests specific to this behavior. - + """ global instance_state, instance_dict, manager_of_class if implementation is util.symbol('native'): diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 367344f5a..8cece65cc 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -65,9 +65,9 @@ class MapperProperty(object): cascade = () """The set of 'cascade' attribute names. - + This collection is checked before the 'cascade_iterator' method is called. - + """ def setup(self, context, entity, path, reduced_path, adapter, **kwargs): @@ -83,7 +83,7 @@ class MapperProperty(object): def create_row_processor(self, selectcontext, path, reduced_path, mapper, row, adapter): """Return a 3-tuple consisting of three row processing functions. - + """ return None, None, None @@ -91,9 +91,9 @@ class MapperProperty(object): halt_on=None): """Iterate through instances related to the given instance for a particular 'cascade', starting with this MapperProperty. - + Return an iterator3-tuples (instance, mapper, state). - + Note that the 'cascade' collection on this MapperProperty is checked first for the given type before cascade_iterator is called. @@ -110,7 +110,7 @@ class MapperProperty(object): _compile_started = False _compile_finished = False - + def init(self): """Called after all mappers are created to assemble relationships between mappers and perform other post-mapper-creation @@ -131,10 +131,10 @@ class MapperProperty(object): def do_init(self): """Perform subclass-specific initialization post-mapper-creation steps. - + This is a template method called by the ``MapperProperty`` object's init() method. - + """ pass @@ -188,7 +188,7 @@ class PropComparator(expression.ColumnOperators): new operator behaivor. The custom :class:`.PropComparator` is passed to the mapper property via the ``comparator_factory`` argument. In each case, the appropriate subclass of :class:`.PropComparator` should be used:: - + from sqlalchemy.orm.properties import \\ ColumnProperty,\\ CompositeProperty,\\ @@ -196,13 +196,13 @@ class PropComparator(expression.ColumnOperators): class MyColumnComparator(ColumnProperty.Comparator): pass - + class MyCompositeComparator(CompositeProperty.Comparator): pass - + class MyRelationshipComparator(RelationshipProperty.Comparator): pass - + """ def __init__(self, prop, mapper, adapter=None): @@ -216,7 +216,7 @@ class PropComparator(expression.ColumnOperators): def adapted(self, adapter): """Return a copy of this PropComparator which will use the given adaption function on the local side of generated expressions. - + """ return self.__class__(self.prop, self.mapper, adapter) @@ -291,9 +291,9 @@ class StrategizedProperty(MapperProperty): There is a single strategy selected by default. Alternate strategies can be selected at Query time through the usage of ``StrategizedOption`` objects via the Query.options() method. - + """ - + def _get_context_strategy(self, context, reduced_path): key = ('loaderstrategy', reduced_path) if key in context.attributes: @@ -334,7 +334,7 @@ class StrategizedProperty(MapperProperty): if self.is_primary() and \ not mapper.class_manager._attr_has_impl(self.key): self.strategy.init_class_attribute(mapper) - + def build_path(entity, key, prev=None): if prev: return prev + (entity, key) @@ -344,7 +344,7 @@ def build_path(entity, key, prev=None): def serialize_path(path): if path is None: return None - + return zip( [m.class_ for m in [path[i] for i in range(0, len(path), 2)]], [path[i] for i in range(1, len(path), 2)] + [None] @@ -366,14 +366,14 @@ class MapperOption(object): """if True, indicate this option should be carried along Query object generated by scalar or object lazy loaders. """ - + def process_query(self, query): pass def process_query_conditionally(self, query): """same as process_query(), except that this option may not apply to the given query. - + Used when secondary loaders resend existing options to a new Query.""" @@ -440,7 +440,7 @@ class PropertyOption(MapperOption): [str(m.path_entity) for m in query._entities])) else: return None - + def _get_paths(self, query, raiseerr): path = None entity = None @@ -451,7 +451,7 @@ class PropertyOption(MapperOption): # existing path current_path = list(query._current_path) - + tokens = deque(self.key) while tokens: token = tokens.popleft() @@ -459,7 +459,7 @@ class PropertyOption(MapperOption): sub_tokens = token.split(".", 1) token = sub_tokens[0] tokens.extendleft(sub_tokens[1:]) - + if not entity: if current_path: if current_path[1] == token: @@ -540,11 +540,11 @@ class StrategizedOption(PropertyOption): def _reduce_path(path): """Convert a (mapper, path) path to use base mappers. - + This is used to allow more open ended selection of loader strategies, i.e. Mapper -> prop1 -> Subclass -> prop2, where Subclass is a sub-mapper of the mapper referened by Mapper.prop1. - + """ return tuple([i % 2 != 0 and element or @@ -595,7 +595,7 @@ class LoaderStrategy(object): row, adapter): """Return row processing functions which fulfill the contract specified by MapperProperty.create_row_processor. - + StrategizedProperty delegates its create_row_processor method directly to this method. """ @@ -617,7 +617,7 @@ class LoaderStrategy(object): class InstrumentationManager(object): """User-defined class instrumentation extension. - + :class:`.InstrumentationManager` can be subclassed in order to change how class instrumentation proceeds. This class exists for @@ -626,13 +626,13 @@ class InstrumentationManager(object): instrumentation methodology of the ORM, and is not intended for regular usage. For interception of class instrumentation events, see :class:`.InstrumentationEvents`. - + For an example of :class:`.InstrumentationManager`, see the example :ref:`examples_instrumentation`. - + The API for this class should be considered as semi-stable, and may change slightly with new releases. - + """ # r4361 added a mandatory (cls) constructor to this interface. @@ -694,4 +694,3 @@ class InstrumentationManager(object): def dict_getter(self, class_): return lambda inst: self.get_instance_dict(class_, inst) -
\ No newline at end of file diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 5dc2fd83d..8fe68fb8c 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -26,7 +26,7 @@ from sqlalchemy.orm import instrumentation, attributes, sync, \ exc as orm_exc, unitofwork, events from sqlalchemy.orm.interfaces import MapperProperty, EXT_CONTINUE, \ PropComparator - + from sqlalchemy.orm.util import _INSTRUMENTOR, _class_to_mapper, \ _state_mapper, class_mapper, instance_str, state_str @@ -111,7 +111,7 @@ class Mapper(object): self.order_by = util.to_list(order_by) else: self.order_by = order_by - + self.always_refresh = always_refresh self.version_id_col = version_id_col self.version_id_generator = version_id_generator or \ @@ -138,16 +138,16 @@ class Mapper(object): self._compiled_cache_size = _compiled_cache_size self._reconstructor = None self._deprecated_extensions = util.to_list(extension or []) - + if allow_null_pks: util.warn_deprecated( "the allow_null_pks option to Mapper() is " "deprecated. It is now allow_partial_pks=False|True, " "defaults to True.") allow_partial_pks = allow_null_pks - + self.allow_partial_pks = allow_partial_pks - + if with_polymorphic == '*': self.with_polymorphic = ('*', None) elif isinstance(with_polymorphic, (tuple, list)): @@ -197,7 +197,7 @@ class Mapper(object): self.exclude_properties = None self.configured = False - + # prevent this mapper from being constructed # while a configure_mappers() is occuring (and defer a configure_mappers() # until construction succeeds) @@ -218,7 +218,7 @@ class Mapper(object): _COMPILE_MUTEX.release() dispatch = event.dispatcher(events.MapperEvents) - + def _configure_inheritance(self): """Configure settings related to inherting and/or inherited mappers being present.""" @@ -322,12 +322,12 @@ class Mapper(object): if self.polymorphic_identity is not None: self.polymorphic_map[self.polymorphic_identity] = self self._identity_class = self.class_ - + if self.mapped_table is None: raise sa_exc.ArgumentError( "Mapper '%s' does not have a mapped_table specified." % self) - + def _configure_legacy_instrument_class(self): if self.inherits: @@ -336,7 +336,7 @@ class Mapper(object): for m in self.inherits.iterate_to_root()])) else: super_extensions = set() - + for ext in self._deprecated_extensions: if ext not in super_extensions: ext._adapt_instrument_class(self, ext) @@ -351,7 +351,7 @@ class Mapper(object): for ext in self._deprecated_extensions: if ext not in super_extensions: ext._adapt_listener(self, ext) - + if self.inherits: self.class_manager.dispatch._update( self.inherits.class_manager.dispatch) @@ -368,7 +368,7 @@ class Mapper(object): """ manager = attributes.manager_of_class(self.class_) - + if self.non_primary: if not manager or not manager.is_mapped: raise sa_exc.InvalidRequestError( @@ -392,7 +392,7 @@ class Mapper(object): # a ClassManager may already exist as # ClassManager.instrument_attribute() creates # new managers for each subclass if they don't yet exist. - + _mapper_registry[self] = True self.dispatch.instrument_class(self, self.class_) @@ -414,7 +414,7 @@ class Mapper(object): event.listen(manager, 'first_init', _event_on_first_init, raw=True) event.listen(manager, 'init', _event_on_init, raw=True) event.listen(manager, 'resurrect', _event_on_resurrect, raw=True) - + for key, method in util.iterate_attributes(self.class_): if isinstance(method, types.FunctionType): if hasattr(method, '__sa_reconstructor__'): @@ -425,31 +425,31 @@ class Mapper(object): self._validators[name] = method manager.info[_INSTRUMENTOR] = self - + @util.deprecated("0.7", message=":meth:`.Mapper.compile` " "is replaced by :func:`.configure_mappers`") def compile(self): """Initialize the inter-mapper relationships of all mappers that have been constructed thus far. - + """ configure_mappers() return self - - + + @property @util.deprecated("0.7", message=":attr:`.Mapper.compiled` " "is replaced by :attr:`.Mapper.configured`") def compiled(self): return self.configured - + def dispose(self): # Disable any attribute-based compilation. self.configured = True - + if hasattr(self, '_configure_failed'): del self._configure_failed - + if not self.non_primary and \ self.class_manager.is_mapped and \ self.class_manager.mapper is self: @@ -465,7 +465,7 @@ class Mapper(object): all_cols = util.column_set(chain(*[ col.proxy_set for col in self._columntoproperty])) - + pk_cols = util.column_set(c for c in all_cols if c.primary_key) # identify primary key columns which are also mapped by this mapper. @@ -489,7 +489,7 @@ class Mapper(object): for col in self._columntoproperty if not hasattr(col, 'table') or col.table not in self._cols_by_table) - + # if explicit PK argument sent, add those columns to the # primary key mappings if self.primary_key_argument: @@ -497,7 +497,7 @@ class Mapper(object): if k.table not in self._pks_by_table: self._pks_by_table[k.table] = util.OrderedSet() self._pks_by_table[k.table].add(k) - + # otherwise, see that we got a full PK for the mapped table elif self.mapped_table not in self._pks_by_table or \ len(self._pks_by_table[self.mapped_table]) == 0: @@ -535,7 +535,7 @@ class Mapper(object): self._log("Identified primary key columns: %s", primary_key) def _configure_properties(self): - + # Column and other ClauseElement objects which are mapped self.columns = self.c = util.OrderedProperties() @@ -589,18 +589,18 @@ class Mapper(object): """Configure an attribute on the mapper representing the 'polymorphic_on' column, if applicable, and not already generated by _configure_properties (which is typical). - + Also create a setter function which will assign this attribute to the value of the 'polymorphic_identity' upon instance construction, also if applicable. This routine will run when an instance is created. - + """ # do a special check for the "discriminiator" column, as it # may only be present in the 'with_polymorphic' selectable # but we need it for the base mapper setter = False - + if self.polymorphic_on is not None: setter = True @@ -624,7 +624,7 @@ class Mapper(object): raise sa_exc.InvalidRequestError( "Cannot exclude or override the discriminator column %r" % col.key) - + self._configure_property( col.key, properties.ColumnProperty(col, _instrument=instrument), @@ -642,7 +642,7 @@ class Mapper(object): self._set_polymorphic_identity = _set_polymorphic_identity else: self._set_polymorphic_identity = None - + def _adapt_inherited_property(self, key, prop, init): @@ -653,7 +653,7 @@ class Mapper(object): key, properties.ConcreteInheritedProperty(), init=init, setparent=True) - + def _configure_property(self, key, prop, init=True, setparent=True): self._log("_configure_property(%s, %s)", key, prop.__class__.__name__) @@ -684,7 +684,7 @@ class Mapper(object): prop.columns.insert(0, column) self._log("inserting column to existing list " "in properties.ColumnProperty %s" % (key)) - + elif prop is None or isinstance(prop, properties.ConcreteInheritedProperty): mapped_column = [] for c in columns: @@ -722,7 +722,7 @@ class Mapper(object): if isinstance(prop, properties.ColumnProperty): col = self.mapped_table.corresponding_column(prop.columns[0]) - + # if the column is not present in the mapped table, # test if a column has been added after the fact to the # parent table (or their parent, etc.) [ticket:1570] @@ -737,7 +737,7 @@ class Mapper(object): prop.columns[0]) break path.append(m) - + # otherwise, col might not be present! the selectable given # to the mapper need not include "deferred" # columns (included in zblog tests) @@ -758,7 +758,7 @@ class Mapper(object): col.table in self._cols_by_table and \ col not in self._cols_by_table[col.table]: self._cols_by_table[col.table].add(col) - + # if this properties.ColumnProperty represents the "polymorphic # discriminator" column, mark it. We'll need this when rendering # columns in SELECT statements. @@ -766,7 +766,7 @@ class Mapper(object): prop._is_polymorphic_discriminator = \ (col is self.polymorphic_on or prop.columns[0] is self.polymorphic_on) - + self.columns[key] = col for col in prop.columns: for col in col.proxy_set: @@ -785,7 +785,7 @@ class Mapper(object): "a ColumnProperty already exists keyed to the name " "%r for column %r" % (syn, key, key, syn) ) - + self._props[key] = prop if not self.non_primary: @@ -805,23 +805,23 @@ class Mapper(object): This is a deferred configuration step which is intended to execute once all mappers have been constructed. - + """ self._log("_post_configure_properties() started") l = [(key, prop) for key, prop in self._props.iteritems()] for key, prop in l: self._log("initialize prop %s", key) - + if prop.parent is self and not prop._compile_started: prop.init() - + if prop._compile_finished: prop.post_instrument_class(self) - + self._log("_post_configure_properties() complete") self.configured = True - + def add_properties(self, dict_of_properties): """Add the given dictionary of properties to this mapper, using `add_property`. @@ -904,19 +904,19 @@ class Mapper(object): except KeyError: raise sa_exc.InvalidRequestError( "Mapper '%s' has no property '%s'" % (self, key)) - + @util.deprecated('0.6.4', 'Call to deprecated function mapper._get_col_to_pr' 'op(). Use mapper.get_property_by_column()') def _get_col_to_prop(self, col): return self._columntoproperty[col] - + def get_property_by_column(self, column): """Given a :class:`.Column` object, return the :class:`.MapperProperty` which maps this column.""" return self._columntoproperty[column] - + @property def iterate_properties(self): """return an iterator of all MapperProperty objects.""" @@ -957,7 +957,7 @@ class Mapper(object): mapped tables. """ - + from_obj = self.mapped_table for m in mappers: if m is self: @@ -1023,7 +1023,7 @@ class Mapper(object): def _iterate_polymorphic_properties(self, mappers=None): """Return an iterator of MapperProperty objects which will render into a SELECT.""" - + if mappers is None: mappers = self._with_polymorphic_mappers @@ -1043,7 +1043,7 @@ class Mapper(object): c.columns[0] is not self.polymorphic_on): continue yield c - + @property def properties(self): raise NotImplementedError( @@ -1109,7 +1109,7 @@ class Mapper(object): hasattr(obj, '__get__') and not \ isinstance(obj.__get__(None, obj), attributes.QueryableAttribute) - + def _should_exclude(self, name, assigned_name, local, column): """determine whether a particular property should be implicitly @@ -1208,13 +1208,13 @@ class Mapper(object): def primary_mapper(self): """Return the primary mapper corresponding to this mapper's class key (class).""" - + return self.class_manager.mapper @property def primary_base_mapper(self): return self.class_manager.mapper.base_mapper - + def identity_key_from_row(self, row, adapter=None): """Return an identity-map key for use in storing/retrieving an item from the identity map. @@ -1261,7 +1261,7 @@ class Mapper(object): impl.get(state, dict_, False) for col in self.primary_key ]) - + def primary_key_from_instance(self, instance): """Return the list of primary key values for the given instance. @@ -1294,7 +1294,7 @@ class Mapper(object): def _get_committed_state_attr_by_column(self, state, dict_, column, passive=False): - + prop = self._columntoproperty[column] return state.manager[prop.key].impl.\ get_committed_value(state, dict_, passive=passive) @@ -1302,21 +1302,21 @@ class Mapper(object): def _optimized_get_statement(self, state, attribute_names): """assemble a WHERE clause which retrieves a given state by primary key, using a minimized set of tables. - + Applies to a joined-table inheritance mapper where the requested attribute names are only present on joined tables, not the base table. The WHERE clause attempts to include only those tables to minimize joins. - + """ props = self._props - + tables = set(chain( *[sqlutil.find_tables(c, check_columns=True) for key in attribute_names for c in props[key].columns] )) - + if self.base_mapper.local_table in tables: return None @@ -1396,7 +1396,7 @@ class Mapper(object): if not iterator: visitables.pop() continue - + if item_type is prp: prop = iterator.popleft() if type_ not in prop.cascade: @@ -1422,7 +1422,7 @@ class Mapper(object): for mapper in self.base_mapper.self_and_descendants: for t in mapper.tables: table_to_mapper[t] = mapper - + sorted_ = sqlutil.sort_tables(table_to_mapper.iterkeys()) ret = util.OrderedDict() for t in sorted_: @@ -1433,15 +1433,15 @@ class Mapper(object): saves = unitofwork.SaveUpdateAll(uow, self.base_mapper) deletes = unitofwork.DeleteAll(uow, self.base_mapper) uow.dependencies.add((saves, deletes)) - + for dep in self._dependency_processors: dep.per_property_preprocessors(uow) - + for prop in self._props.values(): prop.per_property_preprocessors(uow) - + def _per_state_flush_actions(self, uow, states, isdelete): - + base_mapper = self.base_mapper save_all = unitofwork.SaveUpdateAll(uow, base_mapper) delete_all = unitofwork.DeleteAll(uow, base_mapper) @@ -1454,20 +1454,20 @@ class Mapper(object): else: action = unitofwork.SaveUpdateState(uow, state, base_mapper) uow.dependencies.add((action, delete_all)) - + yield action - + def _memo(self, key, callable_): if key in self._memoized_values: return self._memoized_values[key] else: self._memoized_values[key] = value = callable_() return value - + def _post_update(self, states, uowtransaction, post_update_cols): """Issue UPDATE statements on behalf of a relationship() which specifies post_update. - + """ cached_connections = util.PopulateDict( lambda conn:conn.execution_options( @@ -1492,7 +1492,7 @@ class Mapper(object): conn = connection mapper = _state_mapper(state) - + tups.append((state, state.dict, mapper, conn)) table_to_mapper = self._sorted_tables @@ -1503,7 +1503,7 @@ class Mapper(object): for state, state_dict, mapper, connection in tups: if table not in mapper._pks_by_table: continue - + pks = mapper._pks_by_table[table] params = {} hasdata = False @@ -1525,7 +1525,7 @@ class Mapper(object): if hasdata: update.append((state, state_dict, params, mapper, connection)) - + if update: mapper = table_to_mapper[table] @@ -1551,7 +1551,7 @@ class Mapper(object): params, mapper, conn in grouper] cached_connections[connection].\ execute(statement, multiparams) - + def _save_obj(self, states, uowtransaction, single=False): """Issue ``INSERT`` and/or ``UPDATE`` statements for a list of objects. @@ -1562,7 +1562,7 @@ class Mapper(object): updates for all descendant mappers. """ - + # if batch=false, call _save_obj separately for each object if not single and not self.batch: for state in _sort_states(states): @@ -1582,19 +1582,19 @@ class Mapper(object): connection_callable = None tups = [] - + for state in _sort_states(states): if connection_callable: conn = connection_callable(self, state.obj()) else: conn = connection - + has_identity = bool(state.key) mapper = _state_mapper(state) instance_key = state.key or mapper._identity_key_from_state(state) row_switch = None - + # call before_XXX extensions if not has_identity: mapper.dispatch.before_insert(mapper, conn, state) @@ -1648,14 +1648,14 @@ class Mapper(object): instance_key, row_switch in tups: if table not in mapper._pks_by_table: continue - + pks = mapper._pks_by_table[table] - + isinsert = not has_identity and not row_switch - + params = {} value_params = {} - + if isinsert: has_all_pks = True for col in mapper._cols_by_table[table]: @@ -1667,7 +1667,7 @@ class Mapper(object): # pending objects prop = mapper._columntoproperty[col] value = state_dict.get(prop.key, None) - + if value is None: if col in pks: has_all_pks = False @@ -1772,7 +1772,7 @@ class Mapper(object): def update_stmt(): clause = sql.and_() - + for col in mapper._pks_by_table[table]: clause.clauses.append(col == sql.bindparam(col._label, type_=col.type)) @@ -1783,13 +1783,13 @@ class Mapper(object): type_=col.type)) return table.update(clause) - + statement = self._memo(('update', table), update_stmt) - + rows = 0 for state, state_dict, params, mapper, \ connection, value_params in update: - + if value_params: c = connection.execute( statement.values(value_params), @@ -1797,7 +1797,7 @@ class Mapper(object): else: c = cached_connections[connection].\ execute(statement, params) - + mapper._postfetch( uowtransaction, table, @@ -1821,7 +1821,7 @@ class Mapper(object): "- versioning cannot be verified." % c.dialect.dialect_description, stacklevel=12) - + if insert: statement = self._memo(('insert', table), table.insert) @@ -1837,7 +1837,7 @@ class Mapper(object): multiparams = [rec[2] for rec in records] c = cached_connections[connection].\ execute(statement, multiparams) - + for (state, state_dict, params, mapper, conn, value_params, has_all_pks), \ last_inserted_params in \ @@ -1851,7 +1851,7 @@ class Mapper(object): c.context.postfetch_cols, last_inserted_params, value_params) - + else: for state, state_dict, params, mapper, \ connection, value_params, \ @@ -1864,7 +1864,7 @@ class Mapper(object): else: result = cached_connections[connection].\ execute(statement, params) - + primary_key = result.context.inserted_primary_key if primary_key is not None: @@ -1942,14 +1942,14 @@ class Mapper(object): equated_pairs, uowtransaction, self.passive_updates) - + @util.memoized_property def _table_to_equated(self): """memoized map of tables to collections of columns to be synchronized upwards to the base mapper.""" - + result = util.defaultdict(list) - + for table in self._sorted_tables: cols = set(table.c) for m in self.iterate_to_root(): @@ -1957,9 +1957,9 @@ class Mapper(object): cols.intersection( [l for l, r in m._inherits_equated_pairs]): result[table].append((m, m._inherits_equated_pairs)) - + return result - + def _delete_obj(self, states, uowtransaction): """Issue ``DELETE`` statements for a list of objects. @@ -1973,13 +1973,13 @@ class Mapper(object): else: connection = uowtransaction.transaction.connection(self) connection_callable = None - + tups = [] cached_connections = util.PopulateDict( lambda conn:conn.execution_options( compiled_cache=self._compiled_cache )) - + for state in _sort_states(states): mapper = _state_mapper(state) @@ -1987,9 +1987,9 @@ class Mapper(object): conn = connection_callable(self, state.obj()) else: conn = connection - + mapper.dispatch.before_delete(mapper, conn, state) - + tups.append((state, state.dict, _state_mapper(state), @@ -1997,7 +1997,7 @@ class Mapper(object): conn)) table_to_mapper = self._sorted_tables - + for table in reversed(table_to_mapper.keys()): delete = util.defaultdict(list) for state, state_dict, mapper, has_identity, connection in tups: @@ -2080,10 +2080,10 @@ class Mapper(object): polymorphic_from=None, only_load_props=None, refresh_state=None, polymorphic_discriminator=None): - + """Produce a mapper level row processor callable which processes rows into mapped instances.""" - + pk_cols = self.primary_key if polymorphic_from or refresh_state: @@ -2112,7 +2112,7 @@ class Mapper(object): new_populators = [] existing_populators = [] load_path = context.query._current_path + path - + def populate_state(state, dict_, row, isnew, only_load_props): if isnew: if context.propagate_options: @@ -2125,7 +2125,7 @@ class Mapper(object): new_populators, existing_populators ) - + if isnew: populators = new_populators else: @@ -2142,7 +2142,7 @@ class Mapper(object): session_identity_map = context.session.identity_map listeners = self.dispatch - + translate_row = listeners.translate_row or None create_instance = listeners.create_instance or None populate_instance = listeners.populate_instance or None @@ -2152,7 +2152,7 @@ class Mapper(object): is_not_primary_key = _none_set.issuperset else: is_not_primary_key = _none_set.issubset - + def _instance(row, result): if translate_row: for fn in translate_row: @@ -2160,7 +2160,7 @@ class Mapper(object): if ret is not EXT_CONTINUE: row = ret break - + if polymorphic_on is not None: discriminator = row[polymorphic_on] if discriminator is not None: @@ -2196,7 +2196,7 @@ class Mapper(object): dict_, self.version_id_col) != \ row[version_id_col]: - + raise orm_exc.StaleDataError( "Instance '%s' has version id '%s' which " "does not match database-loaded version id '%s'." @@ -2268,7 +2268,7 @@ class Mapper(object): populate_state(state, dict_, row, isnew, only_load_props) else: populate_state(state, dict_, row, isnew, only_load_props) - + if loaded_instance: state.manager.dispatch.load(state, context) elif isnew: @@ -2286,8 +2286,8 @@ class Mapper(object): isnew = True attrs = state.unloaded # allow query.instances to commit the subset of attrs - context.partials[state] = (dict_, attrs) - + context.partials[state] = (dict_, attrs) + if populate_instance: for fn in populate_instance: ret = fn(self, context, row, state, @@ -2299,11 +2299,11 @@ class Mapper(object): populate_state(state, dict_, row, isnew, attrs) else: populate_state(state, dict_, row, isnew, attrs) - + if isnew: state.manager.dispatch.refresh(state, context, attrs) - + if result is not None: if append_result: for fn in append_result: @@ -2322,7 +2322,7 @@ class Mapper(object): def _populators(self, context, path, reduced_path, row, adapter, new_populators, existing_populators): """Produce a collection of attribute level row processor callables.""" - + delayed_populators = [] for prop in self._props.itervalues(): newpop, existingpop, delayedpop = prop.create_row_processor( @@ -2337,11 +2337,11 @@ class Mapper(object): delayed_populators.append((prop.key, delayedpop)) if delayed_populators: new_populators.extend(delayed_populators) - + def _configure_subclass_mapper(self, context, path, reduced_path, adapter): """Produce a mapper level row processor callable factory for mappers inheriting this one.""" - + def configure_subclass_mapper(discriminator): try: mapper = self.polymorphic_map[discriminator] @@ -2351,16 +2351,16 @@ class Mapper(object): discriminator) if mapper is self: return None - + # replace the tip of the path info with the subclass mapper # being used. that way accurate "load_path" info is available # for options invoked during deferred loads. # we lose AliasedClass path elements this way, but currently, # those are not needed at this stage. - + # this asserts to true #assert mapper.isa(_class_to_mapper(path[-1])) - + return mapper._instance_processor(context, path[0:-1] + (mapper,), reduced_path[0:-1] + (mapper.base_mapper,), adapter, @@ -2375,7 +2375,7 @@ def configure_mappers(): This function can be called any number of times, but in most cases is handled internally. - + """ global _new_mappers @@ -2453,7 +2453,7 @@ def validates(*names): can then raise validation exceptions to halt the process from continuing, or can modify or replace the value before proceeding. The function should otherwise return the given value. - + Note that a validator for a collection **cannot** issue a load of that collection within the validation routine - this usage raises an assertion to avoid recursion overflows. This is a reentrant @@ -2477,7 +2477,7 @@ def _event_on_first_init(manager, cls): if instrumenting_mapper: if _new_mappers: configure_mappers() - + def _event_on_init(state, args, kwargs): """Run init_instance hooks.""" @@ -2494,14 +2494,14 @@ def _event_on_resurrect(state): for col, val in zip(instrumenting_mapper.primary_key, state.key[1]): instrumenting_mapper._set_state_attr_by_column( state, state.dict, col, val) - - + + def _sort_states(states): return sorted(states, key=operator.attrgetter('sort_key')) def _load_scalar_attributes(state, attribute_names): """initiate a column-based attribute refresh operation.""" - + mapper = _state_mapper(state) session = sessionlib._state_session(state) if not session: @@ -2511,7 +2511,7 @@ def _load_scalar_attributes(state, attribute_names): (state_str(state))) has_key = bool(state.key) - + result = False if mapper.inherits and not mapper.concrete: @@ -2536,7 +2536,7 @@ def _load_scalar_attributes(state, attribute_names): " persistent and does not " "contain a full primary key." % state_str(state)) identity_key = mapper._identity_key_from_state(state) - + if (_none_set.issubset(identity_key) and \ not mapper.allow_partial_pks) or \ _none_set.issuperset(identity_key): @@ -2545,7 +2545,7 @@ def _load_scalar_attributes(state, attribute_names): "(and shouldn't be expired, either)." % state_str(state)) return - + result = session.query(mapper)._load_on_ident( identity_key, refresh_state=state, diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 9f2d63364..813be60ce 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -62,7 +62,7 @@ class ColumnProperty(StrategizedProperty): self.descriptor = kwargs.pop('descriptor', None) self.extension = kwargs.pop('extension', None) self.active_history = kwargs.pop('active_history', False) - + if 'doc' in kwargs: self.doc = kwargs.pop('doc') else: @@ -73,7 +73,7 @@ class ColumnProperty(StrategizedProperty): break else: self.doc = None - + if kwargs: raise TypeError( "%s received unexpected keyword argument(s): %s" % ( @@ -87,11 +87,11 @@ class ColumnProperty(StrategizedProperty): self.strategy_class = strategies.DeferredColumnLoader else: self.strategy_class = strategies.ColumnLoader - + def instrument_class(self, mapper): if not self.instrument: return - + attributes.register_descriptor( mapper.class_, self.key, @@ -100,7 +100,7 @@ class ColumnProperty(StrategizedProperty): property_=self, doc=self.doc ) - + def do_init(self): super(ColumnProperty, self).do_init() if len(self.columns) > 1 and \ @@ -127,7 +127,7 @@ class ColumnProperty(StrategizedProperty): dest_dict, load, _recursive): if self.key in source_dict: value = source_dict[self.key] - + if not load: dest_dict[self.key] = value else: @@ -136,7 +136,7 @@ class ColumnProperty(StrategizedProperty): else: if dest_state.has_identity and self.key not in dest_dict: dest_state.expire_attributes(dest_dict, [self.key]) - + class Comparator(PropComparator): @util.memoized_instancemethod def __clause_element__(self): @@ -146,17 +146,17 @@ class ColumnProperty(StrategizedProperty): return self.prop.columns[0]._annotate({ "parententity": self.mapper, "parentmapper":self.mapper}) - + def operate(self, op, *other, **kwargs): return op(self.__clause_element__(), *other, **kwargs) def reverse_operate(self, op, other, **kwargs): col = self.__clause_element__() return op(col._bind_param(op, other), col, **kwargs) - + # TODO: legacy..do we need this ? (0.5) ColumnComparator = Comparator - + def __str__(self): return str(self.parent.class_.__name__) + "." + self.key @@ -222,7 +222,7 @@ class RelationshipProperty(StrategizedProperty): RelationshipProperty.Comparator self.comparator = self.comparator_factory(self, None) util.set_creation_order(self) - + if strategy_class: self.strategy_class = strategy_class elif self.lazy== 'dynamic': @@ -230,7 +230,7 @@ class RelationshipProperty(StrategizedProperty): self.strategy_class = dynamic.DynaLoader else: self.strategy_class = strategies.factory(self.lazy) - + self._reverse_property = set() if cascade is not False: @@ -280,13 +280,13 @@ class RelationshipProperty(StrategizedProperty): """Return a copy of this PropComparator which will use the given adaption function on the local side of generated expressions. - + """ return self.__class__(self.property, self.mapper, getattr(self, '_of_type', None), adapter) - + @property def parententity(self): return self.property.parent @@ -314,9 +314,9 @@ class RelationshipProperty(StrategizedProperty): raise NotImplementedError('in_() not yet supported for ' 'relationships. For a simple many-to-one, use ' 'in_() against the set of foreign key values.') - + __hash__ = None - + def __eq__(self, other): if isinstance(other, (NoneType, expression._Null)): if self.property.direction in [ONETOMANY, MANYTOMANY]: @@ -352,7 +352,7 @@ class RelationshipProperty(StrategizedProperty): source_selectable = self.__clause_element__() else: source_selectable = None - + pj, sj, source, dest, secondary, target_adapter = \ self.property._create_joins(dest_polymorphic=True, dest_selectable=to_selectable, @@ -364,7 +364,7 @@ class RelationshipProperty(StrategizedProperty): criterion = crit else: criterion = criterion & crit - + # annotate the *local* side of the join condition, in the case # of pj + sj this is the full primaryjoin, in the case of just # pj its the local side of the primaryjoin. @@ -372,7 +372,7 @@ class RelationshipProperty(StrategizedProperty): j = _orm_annotate(pj) & sj else: j = _orm_annotate(pj, exclude=self.property.remote_side) - + if criterion is not None and target_adapter: # limit this adapter to annotated only? criterion = target_adapter.traverse(criterion) @@ -384,9 +384,9 @@ class RelationshipProperty(StrategizedProperty): # to anything in the enclosing query. if criterion is not None: criterion = criterion._annotate({'_halt_adapt': True}) - + crit = j & criterion - + return sql.exists([1], crit, from_obj=dest).correlate(source) def any(self, criterion=None, **kwargs): @@ -422,26 +422,26 @@ class RelationshipProperty(StrategizedProperty): def __negated_contains_or_equals(self, other): if self.property.direction == MANYTOONE: state = attributes.instance_state(other) - + def state_bindparam(x, state, col): o = state.obj() # strong ref return sql.bindparam(x, unique=True, callable_=lambda : \ self.property.mapper._get_committed_attr_by_column(o, col)) - + def adapt(col): if self.adapter: return self.adapter(col) else: return col - + if self.property._use_get: return sql.and_(*[ sql.or_( adapt(x) != state_bindparam(adapt(x), state, y), adapt(x) == None) for (x, y) in self.property.local_remote_pairs]) - + criterion = sql.and_(*[x==y for (x, y) in zip( self.property.mapper.primary_key, @@ -531,11 +531,11 @@ class RelationshipProperty(StrategizedProperty): if load: # for a full merge, pre-load the destination collection, # so that individual _merge of each item pulls from identity - # map for those already present. + # map for those already present. # also assumes CollectionAttrbiuteImpl behavior of loading # "old" list in any case dest_state.get_impl(self.key).get(dest_state, dest_dict) - + dest_list = [] for current in instances: current_state = attributes.instance_state(current) @@ -545,7 +545,7 @@ class RelationshipProperty(StrategizedProperty): load=load, _recursive=_recursive) if obj is not None: dest_list.append(obj) - + if not load: coll = attributes.init_state_collection(dest_state, dest_dict, self.key) @@ -582,28 +582,28 @@ class RelationshipProperty(StrategizedProperty): if type_ == 'save-update': tuples = state.manager[self.key].impl.\ get_all_pending(state, dict_) - + else: tuples = state.value_as_iterable(dict_, self.key, passive=passive) - + skip_pending = type_ == 'refresh-expire' and 'delete-orphan' \ not in self.cascade - + for instance_state, c in tuples: if instance_state in visited_states: continue - + instance_dict = attributes.instance_dict(c) - + if halt_on and halt_on(instance_state): continue - + if skip_pending and not instance_state.key: continue - + instance_mapper = instance_state.manager.mapper - + if not instance_mapper.isa(self.mapper.class_manager.mapper): raise AssertionError("Attribute '%s' on class '%s' " "doesn't handle objects " @@ -616,13 +616,13 @@ class RelationshipProperty(StrategizedProperty): visited_states.add(instance_state) yield c, instance_mapper, instance_state, instance_dict - + def _add_reverse_property(self, key): other = self.mapper.get_property(key, _compile_mappers=False) self._reverse_property.add(other) other._reverse_property.add(self) - + if not other._get_target().common_parent(self.parent): raise sa_exc.ArgumentError('reverse_property %r on ' 'relationship %s references relationship %s, which ' @@ -634,7 +634,7 @@ class RelationshipProperty(StrategizedProperty): 'both of the same direction %r. 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() @@ -667,7 +667,7 @@ class RelationshipProperty(StrategizedProperty): % (self.key, type(self.argument))) assert isinstance(self.mapper, mapper.Mapper), self.mapper return self.mapper - + def _process_dependent_arguments(self): # accept callables for other attributes which may require @@ -783,23 +783,23 @@ class RelationshipProperty(StrategizedProperty): """Given a join condition, figure out what columns are foreign and are part of a binary "equated" condition to their referecned columns, and convert into a list of tuples of (primary col->foreign col). - + Make several attempts to determine if cols are compared using "=" or other comparators (in which case suggest viewonly), columns are present but not part of the expected mappings, columns don't have any :class:`ForeignKey` information on them, or the ``foreign_keys`` attribute is being used incorrectly. - + """ eq_pairs = criterion_as_pairs(join_condition, consider_as_foreign_keys=self._user_defined_foreign_keys, any_operator=self.viewonly) - + eq_pairs = [(l, r) for (l, r) in eq_pairs if self._col_is_part_of_mappings(l) and self._col_is_part_of_mappings(r) or self.viewonly and r in self._user_defined_foreign_keys] - + if not eq_pairs and \ self.secondary is not None and \ not self._user_defined_foreign_keys: @@ -822,12 +822,12 @@ class RelationshipProperty(StrategizedProperty): join_condition, self )) - + if not eq_pairs: if not self.viewonly and criterion_as_pairs(join_condition, consider_as_foreign_keys=self._user_defined_foreign_keys, any_operator=True): - + err = "Could not locate any "\ "foreign-key-equated, locally mapped column "\ "pairs for %s "\ @@ -836,7 +836,7 @@ class RelationshipProperty(StrategizedProperty): join_condition, self ) - + if not self._user_defined_foreign_keys: err += " Ensure that the "\ "referencing Column objects have a "\ @@ -844,7 +844,7 @@ class RelationshipProperty(StrategizedProperty): "of a ForeignKeyConstraint on their parent "\ "Table, or specify the foreign_keys parameter "\ "to this relationship." - + err += " For more "\ "relaxed rules on join conditions, the "\ "relationship may be marked as viewonly=True." @@ -981,7 +981,7 @@ class RelationshipProperty(StrategizedProperty): util.warn("On %s, 'passive_deletes' is normally configured " "on one-to-many, one-to-one, many-to-many relationships only." % self) - + def _determine_local_remote_pairs(self): if not self.local_remote_pairs: if self.remote_side: @@ -1054,7 +1054,7 @@ class RelationshipProperty(StrategizedProperty): "created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__)) - + def _generate_backref(self): if not self.is_primary(): return @@ -1099,7 +1099,7 @@ class RelationshipProperty(StrategizedProperty): mapper._configure_property(backref_key, relationship) if self.back_populates: self._add_reverse_property(self.back_populates) - + def _post_init(self): self.logger.info('%s setup primary join %s', self, self.primaryjoin) @@ -1121,7 +1121,7 @@ class RelationshipProperty(StrategizedProperty): if not self.viewonly: self._dependency_processor = \ dependency.DependencyProcessor.from_relationship(self) - + @util.memoized_property def _use_get(self): """memoize the 'use_get' attribute of this RelationshipLoader's @@ -1129,7 +1129,7 @@ class RelationshipProperty(StrategizedProperty): strategy = self._get_strategy(strategies.LazyLoader) return strategy.use_get - + def _refers_to_parent_table(self): for c, f in self.synchronize_pairs: if c.table is f.table: @@ -1169,21 +1169,21 @@ class RelationshipProperty(StrategizedProperty): primaryjoin, secondaryjoin, secondary = self.primaryjoin, \ self.secondaryjoin, self.secondary - + # adjust the join condition for single table inheritance, # in the case that the join is to a subclass # this is analgous to the "_adjust_for_single_table_inheritance()" # method in Query. dest_mapper = of_type or self.mapper - + single_crit = dest_mapper._single_table_criterion if single_crit is not None: if secondaryjoin is not None: secondaryjoin = secondaryjoin & single_crit else: primaryjoin = primaryjoin & single_crit - + if aliased: if secondary is not None: secondary = secondary.alias() diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 53c777ae8..d5f0ef0ca 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -55,20 +55,20 @@ def _generative(*assertions): class Query(object): """ORM-level SQL construction object. - + :class:`.Query` is the source of all SELECT statements generated by the ORM, both those formulated by end-user query operations as well as by high level internal operations such as related collection loading. It features a generative interface whereby successive calls return a new :class:`.Query` object, a copy of the former with additional criteria and options associated with it. - + :class:`.Query` objects are normally initially generated using the :meth:`~.Session.query` method of :class:`.Session`. For a full walkthrough of :class:`.Query` usage, see the :ref:`ormtutorial_toplevel`. - + """ - + _enable_eagerloads = True _enable_assertions = True _with_labels = False @@ -100,7 +100,7 @@ class Query(object): _with_options = () _with_hints = () _enable_single_crit = True - + def __init__(self, entities, session=None): self.session = session self._polymorphic_adapters = {} @@ -173,7 +173,7 @@ class Query(object): equivs = self.__all_equivs() self._from_obj_alias = sql_util.ColumnAdapter( self._from_obj[0], equivs) - + def _get_polymorphic_adapter(self, entity, selectable): self.__mapper_loads_polymorphically_with(entity.mapper, sql_util.ColumnAdapter(selectable, @@ -226,7 +226,7 @@ class Query(object): @_generative() def _adapt_all_clauses(self): self._disable_orm_filtering = True - + def _adapt_col_list(self, cols): return [ self._adapt_clause( @@ -234,7 +234,7 @@ class Query(object): True, True) for o in cols ] - + def _adapt_clause(self, clause, as_filter, orm_only): adapters = [] if as_filter and self._filter_aliases: @@ -323,7 +323,7 @@ class Query(object): def _get_condition(self): self._order_by = self._distinct = False return self._no_criterion_condition("get") - + def _no_criterion_condition(self, meth): if not self._enable_assertions: return @@ -407,11 +407,11 @@ class Query(object): @property def statement(self): """The full SELECT statement represented by this Query. - + The statement by default will not have disambiguating labels applied to the construct unless with_labels(True) is called first. - + """ stmt = self._compile_context(labels=self._with_labels).\ @@ -432,33 +432,33 @@ class Query(object): """ return self.enable_eagerloads(False).statement.alias() - + def label(self, name): """Return the full SELECT statement represented by this :class:`.Query`, converted to a scalar subquery with a label of the given name. - + Analagous to :meth:`sqlalchemy.sql._SelectBaseMixin.label`. - + New in 0.6.5. """ - + return self.enable_eagerloads(False).statement.label(name) def as_scalar(self): """Return the full SELECT statement represented by this :class:`.Query`, converted to a scalar subquery. - + Analagous to :meth:`sqlalchemy.sql._SelectBaseMixin.as_scalar`. New in 0.6.5. - + """ - + return self.enable_eagerloads(False).statement.as_scalar() - - + + def __clause_element__(self): return self.enable_eagerloads(False).with_labels().statement @@ -495,11 +495,11 @@ class Query(object): """ self._with_labels = True - + @_generative() def enable_assertions(self, value): """Control whether assertions are generated. - + When set to False, the returned Query will not assert its state before certain operations, including that LIMIT/OFFSET has not been applied @@ -509,22 +509,22 @@ class Query(object): is called. This more permissive mode is used by custom Query subclasses to specify criterion or other modifiers outside of the usual usage patterns. - + Care should be taken to ensure that the usage pattern is even possible. A statement applied by from_statement() will override any criterion set by filter() or order_by(), for example. - + """ self._enable_assertions = value - + @property def whereclause(self): """A readonly attribute which returns the current WHERE criterion for this Query. - + This returned value is a SQL expression construct, or ``None`` if no criterion has been established. - + """ return self._criterion @@ -601,12 +601,12 @@ class Query(object): set the ``stream_results`` execution option to ``True``, which currently is only understood by psycopg2 and causes server side cursors to be used. - + """ self._yield_per = count self._execution_options = self._execution_options.copy() self._execution_options['stream_results'] = True - + def get(self, ident): """Return an instance of the object based on the given identifier, or None if not found. @@ -621,9 +621,9 @@ class Query(object): # convert composite types to individual args if hasattr(ident, '__composite_values__'): ident = ident.__composite_values__() - + ident = util.to_list(ident) - + mapper = self._only_mapper_zero( "get() can only be used against a single mapped class." ) @@ -633,13 +633,13 @@ class Query(object): "Incorrect number of values in identifier to formulate " "primary key for query.get(); primary key columns are %s" % ','.join("'%s'" % c for c in mapper.primary_key)) - + key = mapper.identity_key_from_primary_key(ident) if not self._populate_existing and \ not mapper.always_refresh and \ self._lockmode is None: - + instance = self._get_from_identity(self.session, key, False) if instance is not None: # reject calls for id in identity map but class @@ -655,22 +655,22 @@ class Query(object): """Return a :class:`.Query` construct which will correlate the given FROM clauses to that of an enclosing :class:`.Query` or :func:`~.expression.select`. - + The method here accepts mapped classes, :func:`.aliased` constructs, and :func:`.mapper` constructs as arguments, which are resolved into expression constructs, in addition to appropriate expression constructs. - + The correlation arguments are ultimately passed to :meth:`.Select.correlate` after coercion to expression constructs. - + The correlation arguments take effect in such cases as when :meth:`.Query.from_self` is used, or when a subquery as returned by :meth:`.Query.subquery` is embedded in another :func:`~.expression.select` construct. - + """ - + self._correlate = self._correlate.union( _orm_selectable(s) for s in args) @@ -691,7 +691,7 @@ class Query(object): def populate_existing(self): """Return a :class:`Query` that will expire and refresh all instances as they are loaded, or reused from the current :class:`.Session`. - + :meth:`.populate_existing` does not improve behavior when the ORM is used normally - the :class:`.Session` object's usual behavior of maintaining a transaction and expiring all attributes @@ -706,16 +706,16 @@ class Query(object): to a child object or collection, using its attribute state as well as an established :func:`.relationship()` configuration. - + The method uses the :func:`.with_parent` function to generate the clause, the result of which is passed to :meth:`.Query.filter`. - + Parameters are the same as :func:`.with_parent`, with the exception that the given property can be None, in which case a search is performed against this :class:`.Query` object's target mapper. - + """ - + if property is None: from sqlalchemy.orm import properties mapper = object_mapper(instance) @@ -767,7 +767,7 @@ class Query(object): @_generative() def _enable_single_crit(self, val): self._enable_single_crit = val - + @_generative() def _from_selectable(self, fromclause): for attr in ('_statement', '_criterion', '_order_by', '_group_by', @@ -805,12 +805,12 @@ class Query(object): # end Py2K except StopIteration: return None - + @_generative() def with_entities(self, *entities): """Return a new :class:`.Query` replacing the SELECT list with the given entities. - + e.g.:: # Users, filtered on some arbitrary criterion @@ -830,11 +830,11 @@ class Query(object): limit(1) New in 0.6.5. - + """ self._set_entities(entities) - - + + @_generative() def add_columns(self, *column): """Add one or more column expressions to the list @@ -853,23 +853,23 @@ class Query(object): False) def add_column(self, column): """Add a column expression to the list of result columns to be returned. - + Pending deprecation: :meth:`.add_column` will be superceded by :meth:`.add_columns`. - + """ - + return self.add_columns(column) def options(self, *args): """Return a new Query object, applying the given list of mapper options. - + Most supplied options regard changing how column- and relationship-mapped attributes are loaded. See the sections :ref:`deferred` and :ref:`loading_toplevel` for reference documentation. - + """ return self._options(False, *args) @@ -894,7 +894,7 @@ class Query(object): def with_hint(self, selectable, text, dialect_name='*'): """Add an indexing hint for the given entity or selectable to this :class:`Query`. - + Functionality is passed straight through to :meth:`~sqlalchemy.sql.expression.Select.with_hint`, with the addition that ``selectable`` can be a @@ -902,16 +902,16 @@ class Query(object): /etc. """ mapper, selectable, is_aliased_class = _entity_info(selectable) - + self._with_hints += ((selectable, text, dialect_name),) - + @_generative() def execution_options(self, **kwargs): """ Set non-SQL options which take effect during execution. - + The options are the same as those accepted by :meth:`sqlalchemy.sql.expression.Executable.execution_options`. - + Note that the ``stream_results`` execution option is enabled automatically if the :meth:`~sqlalchemy.orm.query.Query.yield_per()` method is used. @@ -982,16 +982,16 @@ class Query(object): def order_by(self, *criterion): """apply one or more ORDER BY criterion to the query and return the newly resulting ``Query`` - + All existing ORDER BY settings can be suppressed by passing ``None`` - this will suppress any ORDER BY configured on mappers as well. - + Alternatively, an existing ORDER BY setting on the Query object can be entirely cancelled by passing ``False`` as the value - use this before calling methods where an ORDER BY is invalid. - + """ if len(criterion) == 1: @@ -1002,7 +1002,7 @@ class Query(object): if criterion[0] is None: self._order_by = None return - + criterion = self._adapt_col_list(criterion) if self._order_by is False or self._order_by is None: @@ -1075,8 +1075,8 @@ class Query(object): SELECT * FROM Z) """ - - + + return self._from_selectable( expression.union(*([self]+ list(q)))) @@ -1200,15 +1200,15 @@ class Query(object): to join from the right endpoint of the most recent join(), instead of from the query's root entity. I.e. any chain of joins, such as:: - + query.join(a, b, c) - + is equivalent to:: - + query.join(a).\\ join(b, from_joinpoint=True).\\ join(c, from_joinpoint=True) - + """ aliased, from_joinpoint = kwargs.pop('aliased', False),\ kwargs.pop('from_joinpoint', False) @@ -1239,13 +1239,13 @@ class Query(object): def _join(self, keys, outerjoin, create_aliases, from_joinpoint): """consumes arguments from join() or outerjoin(), places them into a consistent format with which to form the actual JOIN constructs. - + """ self._polymorphic_adapters = self._polymorphic_adapters.copy() if not from_joinpoint: self._reset_joinpoint() - + if len(keys) == 2 and \ isinstance(keys[0], (expression.FromClause, type, AliasedClass)) and \ @@ -1264,7 +1264,7 @@ class Query(object): arg1, arg2 = arg1 else: arg2 = None - + # determine onclause/right_entity. there # is a little bit of legacy behavior still at work here # which means they might be in either order. may possibly @@ -1275,19 +1275,19 @@ class Query(object): right_entity, onclause = arg1, arg2 left_entity = prop = None - + if isinstance(onclause, basestring): left_entity = self._joinpoint_zero() descriptor = _entity_descriptor(left_entity, onclause) onclause = descriptor - + # check for q.join(Class.propname, from_joinpoint=True) # and Class is that of the current joinpoint elif from_joinpoint and \ isinstance(onclause, interfaces.PropComparator): left_entity = onclause.parententity - + left_mapper, left_selectable, left_is_aliased = \ _entity_info(self._joinpoint_zero()) if left_mapper is left_entity: @@ -1304,9 +1304,9 @@ class Query(object): right_entity = of_type else: right_entity = onclause.property.mapper - + left_entity = onclause.parententity - + prop = onclause.property if not isinstance(onclause, attributes.QueryableAttribute): onclause = prop @@ -1324,7 +1324,7 @@ class Query(object): elif onclause is not None and right_entity is None: # TODO: no coverage here raise NotImplementedError("query.join(a==b) not supported.") - + self._join_left_to_right( left_entity, right_entity, onclause, @@ -1333,7 +1333,7 @@ class Query(object): def _join_left_to_right(self, left, right, onclause, outerjoin, create_aliases, prop): """append a JOIN to the query's from clause.""" - + if left is None: left = self._joinpoint_zero() @@ -1343,7 +1343,7 @@ class Query(object): "Can't construct a join from %s to %s, they " "are the same entity" % (left, right)) - + left_mapper, left_selectable, left_is_aliased = _entity_info(left) right_mapper, right_selectable, right_is_aliased = _entity_info(right) @@ -1410,7 +1410,7 @@ class Query(object): self._joinpoint = { '_joinpoint_entity':right } - + # if an alias() of the right side was generated here, # apply an adapter to all subsequent filter() calls # until reset_joinpoint() is called. @@ -1423,7 +1423,7 @@ class Query(object): # adapters that are in place right now if isinstance(onclause, expression.ClauseElement): onclause = self._adapt_clause(onclause, True, True) - + # if an alias() on the right side was generated, # which is intended to wrap a the right side in a subquery, # ensure that columns retrieved from this target in the result @@ -1436,7 +1436,7 @@ class Query(object): equivalents=right_mapper._equivalent_columns ) ) - + # this is an overly broad assumption here, but there's a # very wide variety of situations where we rely upon orm.join's # adaption to glue clauses together, with joined-table inheritance's @@ -1446,7 +1446,7 @@ class Query(object): # adaption should be enabled (or perhaps that we're even doing the # whole thing the way we are here). join_to_left = not right_is_aliased and not left_is_aliased - + if self._from_obj and left_selectable is not None: replace_clause_index, clause = sql_util.find_join_source( self._from_obj, @@ -1457,13 +1457,13 @@ class Query(object): # ensure it adapts to the left side. if self._from_obj_alias and clause is self._from_obj[0]: join_to_left = True - + # An exception case where adaption to the left edge is not # desirable. See above note on join_to_left. if join_to_left and isinstance(clause, expression.Join) and \ sql_util.clause_is_present(left_selectable, clause): join_to_left = False - + clause = orm_join(clause, right, onclause, isouter=outerjoin, @@ -1491,7 +1491,7 @@ class Query(object): clause = orm_join(clause, right, onclause, isouter=outerjoin, join_to_left=join_to_left) - + self._from_obj = self._from_obj + (clause,) def _reset_joinpoint(self): @@ -1513,16 +1513,16 @@ class Query(object): @_generative(_no_clauseelement_condition) def select_from(self, *from_obj): """Set the FROM clause of this :class:`.Query` explicitly. - + Sending a mapped class or entity here effectively replaces the "left edge" of any calls to :meth:`.Query.join`, when no joinpoint is otherwise established - usually, the default "join point" is the leftmost entity in the :class:`.Query` object's list of entities to be selected. - + Mapped entities or plain :class:`.Table` or other selectables can be sent here which will form the default FROM clause. - + """ obj = [] for fo in from_obj: @@ -1534,10 +1534,10 @@ class Query(object): raise sa_exc.ArgumentError( "select_from() accepts FromClause objects only.") else: - obj.append(fo) - + obj.append(fo) + self._set_select_from(*obj) - + def __getitem__(self, item): if isinstance(item, slice): start, stop, step = util.decode_slice(item) @@ -1568,7 +1568,7 @@ class Query(object): def slice(self, start, stop): """apply LIMIT/OFFSET to the ``Query`` based on a " "range and return the newly resulting ``Query``.""" - + if start is not None and stop is not None: self._offset = (self._offset or 0) + start self._limit = stop - start @@ -1637,7 +1637,7 @@ class Query(object): def first(self): """Return the first result of this ``Query`` or None if the result doesn't contain any row. - + first() applies a limit of one within the generated SQL, so that only one primary entity row is generated on the server side (note this may consist of multiple result rows if join-loaded @@ -1663,7 +1663,7 @@ class Query(object): if multiple object identities are returned, or if multiple rows are returned for a query that does not return object identities. - + Note that an entity query, that is, one which selects one or more mapped classes as opposed to individual column attributes, may ultimately represent many rows but only one row of @@ -1676,7 +1676,7 @@ class Query(object): """ ret = list(self) - + l = len(ret) if l == 1: return ret[0] @@ -1726,20 +1726,20 @@ class Query(object): clause = querycontext.statement, close_with_result=True).execute(querycontext.statement, self._params) return self.instances(result, querycontext) - + @property def column_descriptions(self): """Return metadata about the columns which would be returned by this :class:`Query`. - + Format is a list of dictionaries:: - + user_alias = aliased(User, name='user2') q = sess.query(User, User.id, user_alias) - + # this expression: q.columns - + # would return: [ { @@ -1761,7 +1761,7 @@ class Query(object): 'expr':user_alias } ] - + """ return [ { @@ -1772,7 +1772,7 @@ class Query(object): } for ent in self._entities ] - + def instances(self, cursor, __context=None): """Given a ResultProxy cursor as returned by connection.execute(), return an ORM result as an iterator. @@ -1810,8 +1810,8 @@ class Query(object): query_entity.row_processor(self, context, custom_rows) for query_entity in self._entities ]) - - + + while True: context.progress = {} context.partials = {} @@ -1855,7 +1855,7 @@ class Query(object): def merge_result(self, iterator, load=True): """Merge a result into this Query's Session. - + Given an iterator returned by a Query of the same structure as this one, return an identical iterator of results, with all mapped instances merged into the session using Session.merge(). This is an @@ -1863,19 +1863,19 @@ class Query(object): structure of the result rows and unmapped columns with less method overhead than that of calling Session.merge() explicitly for each value. - + The structure of the results is determined based on the column list of this Query - if these do not correspond, unchecked errors will occur. - + The 'load' argument is the same as that of Session.merge(). - + """ - + session = self.session if load: # flush current contents if we expect to load data session._autoflush() - + autoflush = session.autoflush try: session.autoflush = False @@ -1900,23 +1900,23 @@ class Query(object): attributes.instance_state(newrow[i]), attributes.instance_dict(newrow[i]), load=load, _recursive={}) - result.append(util.NamedTuple(newrow, row._labels)) - + result.append(util.NamedTuple(newrow, row._labels)) + return iter(result) finally: session.autoflush = autoflush - + @classmethod def _get_from_identity(cls, session, key, passive): """Look up the given key in the given session's identity map, check the object for expired state if found. - + """ instance = session.identity_map.get(key) if instance: - + state = attributes.instance_state(instance) - + # expired - ensure it still exists if state.expired: if passive is attributes.PASSIVE_NO_FETCH: @@ -1930,18 +1930,18 @@ class Query(object): return instance else: return None - + def _load_on_ident(self, key, refresh_state=None, lockmode=None, only_load_props=None): """Load the given identity key from the database.""" - + lockmode = lockmode or self._lockmode if key is not None: ident = key[1] else: ident = None - + if refresh_state is None: q = self._clone() q._get_condition() @@ -1952,7 +1952,7 @@ class Query(object): mapper = self._mapper_zero() (_get_clause, _get_params) = mapper._get_clause - + # None present in ident - turn those comparisons # into "IS NULL" if None in ident: @@ -1962,7 +1962,7 @@ class Query(object): ]) _get_clause = sql_util.adapt_criterion_to_null( _get_clause, nones) - + _get_clause = q._adapt_clause(_get_clause, True, False) q._criterion = _get_clause @@ -2006,7 +2006,7 @@ class Query(object): def count(self): """Return a count of rows this Query would return. - + For simple entity queries, count() issues a SELECT COUNT, and will specifically count the primary key column of the first entity only. If the query uses @@ -2014,11 +2014,11 @@ class Query(object): generated by this Query in a subquery, from which a SELECT COUNT is issued, so that the contract of "how many rows would be returned?" is honored. - + For queries that request specific columns or expressions, count() again makes no assumptions about those expressions and will wrap everything in a subquery. Therefore, - ``Query.count()`` is usually not what you want in this case. + ``Query.count()`` is usually not what you want in this case. To count specific columns, often in conjunction with GROUP BY, use ``func.count()`` as an individual column expression instead of ``Query.count()``. See the ORM tutorial @@ -2081,7 +2081,7 @@ class Query(object): :param synchronize_session: chooses the strategy for the removal of matched objects from the session. Valid values are: - + False - don't synchronize the session. This option is the most efficient and is reliable once the session is expired, which typically occurs after a commit(), or explicitly using @@ -2099,7 +2099,7 @@ class Query(object): the objects in the session. If evaluation of the criteria isn't implemented, an error is raised. In that case you probably want to use the 'fetch' strategy as a fallback. - + The expression evaluator currently doesn't account for differing string collations between the database and Python. @@ -2149,7 +2149,7 @@ class Query(object): else: def eval_condition(obj): return True - + except evaluator.UnevaluatableError: raise sa_exc.InvalidRequestError( "Could not evaluate current criteria in Python. " @@ -2214,7 +2214,7 @@ class Query(object): expire_all(). Before the expiration, updated objects may still remain in the session with stale values on their attributes, which can lead to confusing results. - + 'fetch' - performs a select query before the update to find objects that are matched by the update query. The updated attributes are expired on matched objects. @@ -2254,7 +2254,7 @@ class Query(object): "the synchronize_session argument of " "query.update() is now called 'fetch'") synchronize_session = 'fetch' - + if synchronize_session not in [False, 'evaluate', 'fetch']: raise sa_exc.ArgumentError( "Valid strategies for session synchronization " @@ -2342,7 +2342,7 @@ class Query(object): session.identity_map[identity_key], [_attr_as_key(k) for k in values] ) - + session.dispatch.after_bulk_update(session, self, context, result) return result.rowcount @@ -2367,21 +2367,21 @@ class Query(object): for entity in self._entities: entity.setup_context(self, context) - + for rec in context.create_eager_joins: strategy = rec[0] strategy(*rec[1:]) - + eager_joins = context.eager_joins.values() if context.from_clause: # "load from explicit FROMs" mode, # i.e. when select_from() or join() is used - froms = list(context.from_clause) + froms = list(context.from_clause) else: # "load from discrete FROMs" mode, # i.e. when each _MappedEntity has its own FROM - froms = context.froms + froms = context.froms if self._enable_single_crit: self._adjust_for_single_inheritance(context) @@ -2422,10 +2422,10 @@ class Query(object): order_by=context.order_by, **self._select_args ) - + for hint in self._with_hints: inner = inner.with_hint(*hint) - + if self._correlate: inner = inner.correlate(*self._correlate) @@ -2439,7 +2439,7 @@ class Query(object): [inner] + context.secondary_columns, for_update=for_update, use_labels=labels) - + if self._execution_options: statement = statement.execution_options( **self._execution_options) @@ -2492,7 +2492,7 @@ class Query(object): for hint in self._with_hints: statement = statement.with_hint(*hint) - + if self._execution_options: statement = statement.execution_options( **self._execution_options) @@ -2516,7 +2516,7 @@ class Query(object): selected from the total results. """ - + for entity, (mapper, adapter, s, i, w) in \ self._mapper_adapter_map.iteritems(): single_crit = mapper._single_table_criterion @@ -2558,7 +2558,7 @@ class _MapperEntity(_QueryEntity): self.entities = [entity] self.entity_zero = self.expr = entity - + def setup_entity(self, entity, mapper, adapter, from_obj, is_aliased_class, with_polymorphic): self.mapper = mapper @@ -2578,8 +2578,8 @@ class _MapperEntity(_QueryEntity): self._reduced_path = (mapper.base_mapper, ) self.entity_zero = mapper self._label_name = self.mapper.class_.__name__ - - + + def set_with_polymorphic(self, query, cls_or_mappers, selectable, discriminator): if cls_or_mappers is None: @@ -2611,14 +2611,14 @@ class _MapperEntity(_QueryEntity): query._entities.append(self) def _get_entity_clauses(self, query, context): - + adapter = None if not self.is_aliased_class and query._polymorphic_adapters: adapter = query._polymorphic_adapters.get(self.mapper, None) if not adapter and self.adapter: adapter = self.adapter - + if adapter: if query._from_obj_alias: ret = adapter.wrap(query._from_obj_alias) @@ -2666,7 +2666,7 @@ class _MapperEntity(_QueryEntity): self._polymorphic_discriminator) return _instance, self._label_name - + def setup_context(self, query, context): adapter = self._get_entity_clauses(query, context) @@ -2688,7 +2688,7 @@ class _MapperEntity(_QueryEntity): self._with_polymorphic) else: poly_properties = self.mapper._polymorphic_properties - + for value in poly_properties: if query._only_load_props and \ value.key not in query._only_load_props: @@ -2718,7 +2718,7 @@ class _ColumnEntity(_QueryEntity): def __init__(self, query, column): self.expr = column - + if isinstance(column, basestring): column = sql.literal_column(column) self._label_name = column.name @@ -2779,17 +2779,17 @@ class _ColumnEntity(_QueryEntity): self.entity_zero = list(self.entities)[0] else: self.entity_zero = None - + @property def type(self): return self.column.type - + def adapt_to_selectable(self, query, sel): c = _ColumnEntity(query, sel.corresponding_column(self.column)) c._label_name = self._label_name c.entity_zero = self.entity_zero c.entities = self.entities - + def setup_entity(self, entity, mapper, adapter, from_obj, is_aliased_class, with_polymorphic): self.selectable = from_obj @@ -2834,7 +2834,7 @@ class QueryContext(object): multi_row_eager_loaders = False adapter = None froms = () - + def __init__(self, query): if query._statement is not None: diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py index 431377da0..1068f6704 100644 --- a/lib/sqlalchemy/orm/scoping.py +++ b/lib/sqlalchemy/orm/scoping.py @@ -22,10 +22,10 @@ class ScopedSession(object): Session = scoped_session(sessionmaker()) ... use Session normally. - + The internal registry is accessible as well, and by default is an instance of :class:`.ThreadLocalRegistry`. - + """ @@ -54,14 +54,14 @@ class ScopedSession(object): def remove(self): """Dispose of the current contextual session.""" - + if self.registry.has(): self.registry().close() self.registry.clear() def configure(self, **kwargs): """reconfigure the sessionmaker used by this ScopedSession.""" - + if self.registry.has(): warn('At least one scoped session is already present. ' ' configure() can not affect sessions that have ' @@ -74,7 +74,7 @@ class ScopedSession(object): class when called. e.g.:: - + Session = scoped_session(sessionmaker()) class MyClass(object): diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 942b4d684..47420e207 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -138,7 +138,7 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False, :param extension: An optional :class:`~.SessionExtension` instance, or a list of such instances, which will receive pre- and post- commit and flush - events, as well as a post-rollback event. **Deprecated.** + events, as well as a post-rollback event. **Deprecated.** Please see :class:`.SessionEvents`. :param query_cls: Class which should be used to create new Query objects, @@ -190,8 +190,8 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False, Session.configure(bind=create_engine('sqlite://')) """ kwargs.update(new_kwargs) - - + + return type("Session", (Sess, class_), {}) @@ -211,9 +211,9 @@ class SessionTransaction(object): single: thread safety; SessionTransaction """ - + _rollback_exception = None - + def __init__(self, session, parent=None, nested=False): self.session = session self._connections = {} @@ -297,7 +297,7 @@ class SessionTransaction(object): for s in set(self._new).union(self.session._new): self.session._expunge_state(s) - + for s in set(self._deleted).union(self.session._deleted): if s.deleted: # assert s in self._deleted @@ -465,7 +465,7 @@ class Session(object): """Manages persistence operations for ORM-mapped objects. The Session's usage paradigm is described at :ref:`session_toplevel`. - + """ public_methods = ( @@ -475,8 +475,8 @@ class Session(object): 'is_modified', 'merge', 'query', 'refresh', 'rollback', 'scalar') - - + + def __init__(self, bind=None, autoflush=True, expire_on_commit=True, _enable_transaction_accounting=True, autocommit=False, twophase=False, @@ -489,7 +489,7 @@ class Session(object): typical point of entry. """ - + if weak_identity_map: self._identity_cls = identity.WeakInstanceDict else: @@ -509,11 +509,11 @@ class Session(object): self._enable_transaction_accounting = _enable_transaction_accounting self.twophase = twophase self._query_cls = query_cls - + if extension: for ext in util.to_list(extension): SessionExtension._adapt_listener(self, ext) - + if binds is not None: for mapperortable, bind in binds.iteritems(): if isinstance(mapperortable, (type, Mapper)): @@ -528,7 +528,7 @@ class Session(object): dispatch = event.dispatcher(SessionEvents) connection_callable = None - + def begin(self, subtransactions=False, nested=False): """Begin a transaction on this Session. @@ -537,9 +537,9 @@ class Session(object): ``subtransactions=True`` or ``nested=True`` is specified. The ``subtransactions=True`` flag indicates that this :meth:`~.Session.begin` - can create a subtransaction if a transaction is already in progress. + can create a subtransaction if a transaction is already in progress. For documentation on subtransactions, please see :ref:`session_subtransactions`. - + The ``nested`` flag begins a SAVEPOINT transaction and is equivalent to calling :meth:`~.Session.begin_nested`. For documentation on SAVEPOINT transactions, please see :ref:`session_begin_nested`. @@ -588,12 +588,12 @@ class Session(object): def commit(self): """Flush pending changes and commit the current transaction. - + If no transaction is in progress, this method raises an InvalidRequestError. - + By default, the :class:`.Session` also expires all database - loaded state on all ORM-managed attributes after transaction commit. + loaded state on all ORM-managed attributes after transaction commit. This so that subsequent operations load the most recent data from the database. This behavior can be disabled using the ``expire_on_commit=False`` option to :func:`.sessionmaker` or @@ -692,7 +692,7 @@ class Session(object): will be created for the life of the result (i.e., a connection is checked out from the connection pool, which is returned when the result object is closed). - + If the :class:`Session` is not bound to an :class:`~sqlalchemy.engine.base.Engine` or :class:`~sqlalchemy.engine.base.Connection`, the given clause will be @@ -702,7 +702,7 @@ class Session(object): (since the :class:`Session` keys multiple bind sources to a series of :func:`mapper` objects). See :meth:`get_bind` for further details on bind resolution. - + :param clause: A ClauseElement (i.e. select(), text(), etc.) or string SQL statement to be executed @@ -716,7 +716,7 @@ class Session(object): :param \**kw: Additional keyword arguments are sent to :meth:`get_bind()` which locates a connectable to use for the execution. - + """ clause = expression._literal_as_text(clause) @@ -727,7 +727,7 @@ class Session(object): def scalar(self, clause, params=None, mapper=None, **kw): """Like execute() but return a scalar result.""" - + return self.execute(clause, params=params, mapper=mapper, **kw).scalar() def close(self): @@ -826,7 +826,7 @@ class Session(object): "a binding.") c_mapper = mapper is not None and _class_to_mapper(mapper) or None - + # manually bound? if self.__binds: if c_mapper: @@ -853,7 +853,7 @@ class Session(object): context.append('mapper %s' % c_mapper) if clause is not None: context.append('SQL expression') - + raise sa_exc.UnboundExecutionError( "Could not locate a bind configured on %s or this Session" % ( ', '.join(context))) @@ -890,14 +890,14 @@ class Session(object): :meth:`~Session.refresh` usually only makes sense if non-ORM SQL statement were emitted in the ongoing transaction, or if autocommit mode is turned on. - + :param attribute_names: optional. An iterable collection of string attribute names indicating a subset of attributes to be refreshed. - + :param lockmode: Passed to the :class:`~sqlalchemy.orm.query.Query` as used by :meth:`~sqlalchemy.orm.query.Query.with_lockmode`. - + """ try: state = attributes.instance_state(instance) @@ -916,7 +916,7 @@ class Session(object): def expire_all(self): """Expires all persistent instances within this Session. - + When any attributes on a persitent instance is next accessed, a query will be issued using the :class:`.Session` object's current transactional context in order to @@ -927,7 +927,7 @@ class Session(object): To expire individual objects and individual attributes on those objects, use :meth:`Session.expire`. - + The :class:`Session` object's default behavior is to expire all state whenever the :meth:`Session.rollback` or :meth:`Session.commit` methods are called, so that new @@ -949,10 +949,10 @@ class Session(object): a highly isolated transaction will return the same values as were previously read in that same transaction, regardless of changes in database state outside of that transaction. - + To expire all objects in the :class:`.Session` simultaneously, use :meth:`Session.expire_all`. - + The :class:`Session` object's default behavior is to expire all state whenever the :meth:`Session.rollback` or :meth:`Session.commit` methods are called, so that new @@ -971,7 +971,7 @@ class Session(object): except exc.NO_STATE: raise exc.UnmappedInstanceError(instance) self._expire_state(state, attribute_names) - + def _expire_state(self, state, attribute_names): self._validate_persistent(state) if attribute_names: @@ -984,16 +984,16 @@ class Session(object): self._conditional_expire(state) for o, m, st_, dct_ in cascaded: self._conditional_expire(st_) - + def _conditional_expire(self, state): """Expire a state if persistent, else expunge if pending""" - + if state.key: state.expire(state.dict, self.identity_map._modified) elif state in self._new: self._new.pop(state) state.detach() - + def prune(self): """Remove unreferenced instances cached in the identity map. @@ -1046,7 +1046,7 @@ class Session(object): if obj is not None: instance_key = mapper._identity_key_from_state(state) - + if _none_set.issubset(instance_key[1]) and \ not mapper.allow_partial_pks or \ _none_set.issuperset(instance_key[1]): @@ -1063,10 +1063,10 @@ class Session(object): # map (see test/orm/test_naturalpks.py ReversePKsTest) self.identity_map.discard(state) state.key = instance_key - + self.identity_map.replace(state) state.commit_all(state.dict, self.identity_map) - + # remove from new last, might be the last strong ref if state in self._new: if self._enable_transaction_accounting and self.transaction: @@ -1132,7 +1132,7 @@ class Session(object): if state in self._deleted: return - + # ensure object is attached to allow the # cascade operation to load deferred attributes # and collections @@ -1164,19 +1164,19 @@ class Session(object): mapped with ``cascade="merge"``. See :ref:`unitofwork_merging` for a detailed discussion of merging. - + """ if 'dont_load' in kw: load = not kw['dont_load'] util.warn_deprecated('dont_load=True has been renamed to ' 'load=False.') - + _recursive = {} - + if load: # flush current contents if we expect to load data self._autoflush() - + _object_mapper(instance) # verify mapped autoflush = self.autoflush try: @@ -1187,7 +1187,7 @@ class Session(object): load=load, _recursive=_recursive) finally: self.autoflush = autoflush - + def _merge(self, state, state_dict, load=True, _recursive=None): mapper = _state_mapper(state) if state in _recursive: @@ -1195,7 +1195,7 @@ class Session(object): new_instance = False key = state.key - + if key is None: if not load: raise sa_exc.InvalidRequestError( @@ -1207,7 +1207,7 @@ class Session(object): if key in self.identity_map: merged = self.identity_map[key] - + elif not load: if state.modified: raise sa_exc.InvalidRequestError( @@ -1219,14 +1219,14 @@ class Session(object): merged_state.key = key self._update_impl(merged_state) new_instance = True - + elif not _none_set.issubset(key[1]) or \ (mapper.allow_partial_pks and not _none_set.issuperset(key[1])): merged = self.query(mapper.class_).get(key[1]) else: merged = None - + if merged is None: merged = mapper.class_manager.new_instance() merged_state = attributes.instance_state(merged) @@ -1236,15 +1236,15 @@ class Session(object): else: merged_state = attributes.instance_state(merged) merged_dict = attributes.instance_dict(merged) - + _recursive[state] = merged # check that we didn't just pull the exact same - # state out. + # state out. if state is not merged_state: merged_state.load_path = state.load_path merged_state.load_options = state.load_options - + for prop in mapper.iterate_properties: prop.merge(self, state, state_dict, merged_state, merged_dict, @@ -1252,7 +1252,7 @@ class Session(object): if not load: # remove any history - merged_state.commit_all(merged_dict, self.identity_map) + merged_state.commit_all(merged_dict, self.identity_map) if new_instance: merged_state.manager.dispatch.load(merged_state) @@ -1279,7 +1279,7 @@ class Session(object): raise sa_exc.InvalidRequestError( "Object '%s' already has an identity - it can't be registered " "as pending" % mapperutil.state_str(state)) - + self._attach(state) if state not in self._new: self._new[state] = state.obj() @@ -1289,12 +1289,12 @@ class Session(object): if (self.identity_map.contains_state(state) and state not in self._deleted): return - + if state.key is None: raise sa_exc.InvalidRequestError( "Instance '%s' is not persisted" % mapperutil.state_str(state)) - + if state.deleted: raise sa_exc.InvalidRequestError( "Instance '%s' has been deleted. Use the make_transient() " @@ -1317,11 +1317,11 @@ class Session(object): if state.key is None: return - + self._attach(state) self._deleted[state] = state.obj() self.identity_map.add(state) - + def _attach(self, state): if state.key and \ state.key in self.identity_map and \ @@ -1330,13 +1330,13 @@ class Session(object): "%s; another instance with key %s is already " "present in this session." % (mapperutil.state_str(state), state.key)) - + if state.session_id and state.session_id is not self.hash_key: raise sa_exc.InvalidRequestError( "Object '%s' is already attached to session '%s' " "(this is '%s')" % (mapperutil.state_str(state), state.session_id, self.hash_key)) - + if state.session_id != self.hash_key: state.session_id = self.hash_key if self.dispatch.after_attach: @@ -1393,16 +1393,16 @@ class Session(object): "The 'objects' argument to session.flush() is deprecated; " "Please do not add objects to the session which should not " "yet be persisted.") - + if self._flushing: raise sa_exc.InvalidRequestError("Session is already flushing") - + try: self._flushing = True self._flush(objects) finally: self._flushing = False - + def _flush(self, objects=None): if (not self.identity_map.check_modified() and not self._deleted and not self._new): @@ -1414,13 +1414,13 @@ class Session(object): return flush_context = UOWTransaction(self) - + if self.dispatch.before_flush: self.dispatch.before_flush(self, flush_context, objects) # re-establish "dirty states" in case the listeners # added dirty = self._dirty_states - + deleted = set(self._deleted) new = set(self._new) @@ -1448,7 +1448,7 @@ class Session(object): proc = new.union(dirty).intersection(objset).difference(deleted) else: proc = new.union(dirty).difference(deleted) - + for state in proc: is_orphan = _state_mapper(state)._is_orphan(state) and state.has_identity flush_context.register_object(state, isdelete=is_orphan) @@ -1475,7 +1475,7 @@ class Session(object): except: transaction.rollback(_capture_exception=True) raise - + flush_context.finalize_flush_changes() # useful assertions: @@ -1485,7 +1485,7 @@ class Session(object): # assert self.identity_map._modified == \ # self.identity_map._modified.difference(objects) #self.identity_map._modified.clear() - + self.dispatch.after_flush_postexec(self, flush_context) def is_modified(self, instance, include_collections=True, passive=False): @@ -1493,7 +1493,7 @@ class Session(object): This method retrieves a history instance for each instrumented attribute on the instance and performs a comparison of the current - value to its previously committed value. + value to its previously committed value. ``include_collections`` indicates if multivalued collections should be included in the operation. Setting this to False is a way to detect @@ -1503,9 +1503,9 @@ class Session(object): The ``passive`` flag indicates if unloaded attributes and collections should not be loaded in the course of performing this test. - + A few caveats to this method apply: - + * Instances present in the 'dirty' collection may result in a value of ``False`` when tested with this method. This because while the object may have received attribute set events, there may be @@ -1520,7 +1520,7 @@ class Session(object): based on the assumption that an UPDATE of the scalar value is usually needed, and in those few cases where it isn't, is less expensive on average than issuing a defensive SELECT. - + The "old" value is fetched unconditionally only if the attribute container has the "active_history" flag set to ``True``. This flag is set typically for primary key attributes and scalar references @@ -1539,10 +1539,10 @@ class Session(object): hasattr(attr.impl, 'get_collection') ) or not hasattr(attr.impl, 'get_history'): continue - + (added, unchanged, deleted) = \ attr.impl.get_history(state, dict_, passive=passive) - + if added or deleted: return True return False @@ -1604,18 +1604,18 @@ _sessions = weakref.WeakValueDictionary() def make_transient(instance): """Make the given instance 'transient'. - + This will remove its association with any session and additionally will remove its "identity key", such that it's as though the object were newly constructed, except retaining its values. It also resets the "deleted" flag on the state if this object had been explicitly deleted by its session. - + Attributes which were "expired" or deferred at the instance level are reverted to undefined, and will not trigger any loads. - + """ state = attributes.instance_state(instance) s = _state_session(state) @@ -1629,19 +1629,19 @@ def make_transient(instance): del state.key if state.deleted: del state.deleted - + def object_session(instance): """Return the ``Session`` to which instance belongs. - + If the instance is not a mapped instance, an error is raised. """ - + try: return _state_session(attributes.instance_state(instance)) except exc.NO_STATE: raise exc.UnmappedInstanceError(instance) - + def _state_session(state): if state.session_id: diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 974b3d500..da91a353e 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -37,14 +37,14 @@ class InstanceState(object): modified = False expired = False deleted = False - + def __init__(self, obj, manager): self.class_ = obj.__class__ self.manager = manager self.obj = weakref.ref(obj, self._cleanup) self.callables = {} self.committed_state = {} - + @util.memoized_property def parents(self): return {} @@ -56,14 +56,14 @@ class InstanceState(object): @property def has_identity(self): return bool(self.key) - + def detach(self): self.session_id = None def dispose(self): self.detach() del self.obj - + def _cleanup(self, ref): instance_dict = self._instance_dict() if instance_dict: @@ -71,14 +71,14 @@ class InstanceState(object): instance_dict.remove(self) except AssertionError: pass - + self.callables = {} self.session_id = None del self.obj - + def obj(self): return None - + @property def dict(self): o = self.obj() @@ -86,7 +86,7 @@ class InstanceState(object): return attributes.instance_dict(o) else: return {} - + @property def sort_key(self): return self.key and self.key[1] or (self.insert_order, ) @@ -96,7 +96,7 @@ class InstanceState(object): manager = self.manager manager.dispatch.init(self, args, kwargs) - + #if manager.mutable_attributes: # assert self.__class__ is MutableAttrInstanceState @@ -148,7 +148,7 @@ class InstanceState(object): if self.load_path: d['load_path'] = interfaces.serialize_path(self.load_path) return d - + def __setstate__(self, state): from sqlalchemy.orm import instrumentation self.obj = weakref.ref(state['instance'], self._cleanup) @@ -162,17 +162,17 @@ class InstanceState(object): self.class_) elif manager.is_mapped and not manager.mapper.configured: mapperlib.configure_mappers() - + self.committed_state = state.get('committed_state', {}) self.pending = state.get('pending', {}) self.parents = state.get('parents', {}) self.modified = state.get('modified', False) self.expired = state.get('expired', False) self.callables = state.get('callables', {}) - + if self.modified: self._strong_obj = state['instance'] - + self.__dict__.update([ (k, state[k]) for k in ( 'key', 'load_options', 'mutable_dict' @@ -181,13 +181,13 @@ class InstanceState(object): if 'load_path' in state: self.load_path = interfaces.deserialize_path(state['load_path']) - + # TODO: need an event here, link to composite, mutable - + def initialize(self, key): """Set this attribute to an empty value or collection, based on the AttributeImpl in use.""" - + self.manager.get_impl(key).initialize(self, self.dict) def reset(self, dict_, key): @@ -212,10 +212,10 @@ class InstanceState(object): def set_callable(self, dict_, key, callable_): """Remove the given attribute and set the given callable as a loader.""" - + dict_.pop(key, None) self.callables[key] = callable_ - + def expire(self, dict_, modified_set): self.expired = True if self.modified: @@ -230,26 +230,26 @@ class InstanceState(object): mutable_dict.clear() if pending: pending.clear() - + for key in self.manager: impl = self.manager[key].impl if impl.accepts_scalar_loader and \ (impl.expire_missing or key in dict_): self.callables[key] = self dict_.pop(key, None) - + self.manager.dispatch.expire(self, None) def expire_attributes(self, dict_, attribute_names): pending = self.__dict__.get('pending', None) mutable_dict = self.mutable_dict - + for key in attribute_names: impl = self.manager[key].impl if impl.accepts_scalar_loader: self.callables[key] = self dict_.pop(key, None) - + self.committed_state.pop(key, None) if mutable_dict: mutable_dict.pop(key, None) @@ -267,10 +267,10 @@ class InstanceState(object): if passive is PASSIVE_NO_FETCH: return PASSIVE_NO_RESULT - + toload = self.expired_attributes.\ intersection(self.unmodified) - + self.manager.deferred_scalar_loader(self, toload) # if the loader failed, or this @@ -279,13 +279,13 @@ class InstanceState(object): # dict. ensure they are removed. for k in toload.intersection(self.callables): del self.callables[k] - + return ATTR_WAS_SET @property def unmodified(self): """Return the set of keys which have no uncommitted changes""" - + return set(self.manager).difference(self.committed_state) def unmodified_intersection(self, keys): @@ -311,11 +311,11 @@ class InstanceState(object): def expired_attributes(self): """Return the set of keys which are 'expired' to be loaded by the manager's deferred scalar loader, assuming no pending - changes. - + changes. + see also the ``unmodified`` collection which is intersected against this set when a refresh operation occurs. - + """ return set([k for k, v in self.callables.items() if v is self]) @@ -324,24 +324,24 @@ class InstanceState(object): def _is_really_none(self): return self.obj() - + def modified_event(self, dict_, attr, previous, collection=False): if attr.key not in self.committed_state: if collection: if previous is NEVER_SET: if attr.key in dict_: previous = dict_[attr.key] - + if previous not in (None, NO_VALUE, NEVER_SET): previous = attr.copy(previous) self.committed_state[attr.key] = previous - + # the "or not self.modified" is defensive at # this point. The assertion below is expected # to be True: # assert self._strong_obj is None or self.modified - + if self._strong_obj is None or not self.modified: instance_dict = self._instance_dict() if instance_dict: @@ -350,7 +350,7 @@ class InstanceState(object): self._strong_obj = self.obj() self.modified = True - + def commit(self, dict_, keys): """Commit attributes. @@ -371,14 +371,14 @@ class InstanceState(object): else: for key in keys: self.committed_state.pop(key, None) - + self.expired = False - + for key in set(self.callables).\ intersection(keys).\ intersection(dict_): del self.callables[key] - + def commit_all(self, dict_, instance_dict=None): """commit all attributes unconditionally. @@ -402,30 +402,30 @@ class InstanceState(object): for key in list(callables): if key in dict_ and callables[key] is self: del callables[key] - + for key in self.manager.mutable_attributes: if key in dict_: self.committed_state[key] = self.manager[key].impl.copy(dict_[key]) - + if instance_dict and self.modified: instance_dict._modified.discard(self) - + self.modified = self.expired = False self._strong_obj = None class MutableAttrInstanceState(InstanceState): """InstanceState implementation for objects that reference 'mutable' attributes. - + Has a more involved "cleanup" handler that checks mutable attributes for changes upon dereference, resurrecting if needed. - + """ - + @util.memoized_property def mutable_dict(self): return {} - + def _get_modified(self, dict_=None): if self.__dict__.get('modified', False): return True @@ -437,44 +437,44 @@ class MutableAttrInstanceState(InstanceState): return True else: return False - + def _set_modified(self, value): self.__dict__['modified'] = value - + modified = property(_get_modified, _set_modified) - + @property def unmodified(self): """a set of keys which have no uncommitted changes""" dict_ = self.dict - + return set([ key for key in self.manager if (key not in self.committed_state or (key in self.manager.mutable_attributes and not self.manager[key].impl.check_mutable_modified(self, dict_)))]) - + def unmodified_intersection(self, keys): """Return self.unmodified.intersection(keys).""" dict_ = self.dict - + return set([ key for key in keys if (key not in self.committed_state or (key in self.manager.mutable_attributes and not self.manager[key].impl.check_mutable_modified(self, dict_)))]) - - + + def _is_really_none(self): """do a check modified/resurrect. - + This would be called in the extremely rare race condition that the weakref returned None but the cleanup handler had not yet established the __resurrect callable as its replacement. - + """ if self.modified: self.obj = self.__resurrect @@ -485,19 +485,19 @@ class MutableAttrInstanceState(InstanceState): def reset(self, dict_, key): self.mutable_dict.pop(key, None) InstanceState.reset(self, dict_, key) - + def _cleanup(self, ref): """weakref callback. - + This method may be called by an asynchronous gc. - + If the state shows pending changes, the weakref is replaced by the __resurrect callable which will re-establish an object reference on next access, else removes this InstanceState from the owning identity map, if any. - + """ if self._get_modified(self.mutable_dict): self.obj = self.__resurrect @@ -509,13 +509,13 @@ class MutableAttrInstanceState(InstanceState): except AssertionError: pass self.dispose() - + def __resurrect(self): """A substitute for the obj() weakref function which resurrects.""" - + # store strong ref'ed version of the object; will revert # to weakref when changes are persisted - + obj = self.manager.new_instance(state=self) self.obj = weakref.ref(obj, self._cleanup) self._strong_obj = obj @@ -523,7 +523,7 @@ class MutableAttrInstanceState(InstanceState): # re-establishes identity attributes from the key self.manager.dispatch.resurrect(self) - + return obj class PendingCollection(object): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index d62bf3771..7d3e563f4 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -32,15 +32,15 @@ def _register_attribute(strategy, mapper, useobject, proxy_property=None, active_history=False, impl_class=None, - **kw + **kw ): prop = strategy.parent_property attribute_ext = list(util.to_list(prop.extension, default=[])) - + listen_hooks = [] - + if useobject and prop.single_parent: listen_hooks.append(single_parent_validator) @@ -50,10 +50,10 @@ def _register_attribute(strategy, mapper, useobject, prop.key, prop.parent._validators[prop.key]) ) - + if useobject: listen_hooks.append(unitofwork.track_cascade_events) - + # need to assemble backref listeners # after the singleparentvalidator, mapper validator backref = kw.pop('backref', None) @@ -63,10 +63,10 @@ def _register_attribute(strategy, mapper, useobject, backref, uselist) ) - + for m in mapper.self_and_descendants: if prop is m._props.get(prop.key): - + desc = attributes.register_attribute_impl( m.class_, prop.key, @@ -85,16 +85,16 @@ def _register_attribute(strategy, mapper, useobject, doc=prop.doc, **kw ) - + for hook in listen_hooks: hook(desc, prop) class UninstrumentedColumnLoader(LoaderStrategy): """Represent the a non-instrumented MapperProperty. - + The polymorphic_on argument of mapper() often results in this, if the argument is against the with_polymorphic selectable. - + """ def init(self): self.columns = self.parent_property.columns @@ -111,24 +111,24 @@ class UninstrumentedColumnLoader(LoaderStrategy): class ColumnLoader(LoaderStrategy): """Strategize the loading of a plain column-based MapperProperty.""" - + def init(self): self.columns = self.parent_property.columns self.is_composite = hasattr(self.parent_property, 'composite_class') - + def setup_query(self, context, entity, path, reduced_path, adapter, column_collection=None, **kwargs): for c in self.columns: if adapter: c = adapter.columns[c] column_collection.append(c) - + def init_class_attribute(self, mapper): self.is_class_level = True coltype = self.columns[0].type # TODO: check all columns ? check for foreign key as well? active_history = self.parent_property.active_history or \ - self.columns[0].primary_key + self.columns[0].primary_key _register_attribute(self, mapper, useobject=False, compare_function=coltype.compare_values, @@ -136,7 +136,7 @@ class ColumnLoader(LoaderStrategy): mutable_scalars=self.columns[0].type.is_mutable(), active_history = active_history ) - + def create_row_processor(self, selectcontext, path, reduced_path, mapper, row, adapter): key = self.key # look through list of columns represented here @@ -189,7 +189,7 @@ class DeferredColumnLoader(LoaderStrategy): def init_class_attribute(self, mapper): self.is_class_level = True - + _register_attribute(self, mapper, useobject=False, compare_function=self.columns[0].type.compare_values, copy_function=self.columns[0].type.copy_value, @@ -207,17 +207,17 @@ class DeferredColumnLoader(LoaderStrategy): self.parent_property._get_strategy(ColumnLoader).\ setup_query(context, entity, path, reduced_path, adapter, **kwargs) - + def _load_for_state(self, state, passive): if not state.key: return attributes.ATTR_EMPTY if passive is attributes.PASSIVE_NO_FETCH: return attributes.PASSIVE_NO_RESULT - + prop = self.parent_property localparent = state.manager.mapper - + if self.group: toload = [ p.key for p in @@ -244,7 +244,7 @@ class DeferredColumnLoader(LoaderStrategy): query._load_on_ident(state.key, only_load_props=group, refresh_state=state) return attributes.ATTR_WAS_SET - + log.class_logger(DeferredColumnLoader) class LoadDeferredColumns(object): @@ -253,7 +253,7 @@ class LoadDeferredColumns(object): def __init__(self, state, key): self.state = state self.key = key - + def __call__(self, passive=False): state, key = self.state, self.key @@ -264,7 +264,7 @@ class LoadDeferredColumns(object): class DeferredOption(StrategizedOption): propagate_to_loaders = True - + def __init__(self, key, defer=False): super(DeferredOption, self).__init__(key) self.defer = defer @@ -280,7 +280,7 @@ class UndeferGroupOption(MapperOption): def __init__(self, group): self.group = group - + def process_query(self, query): query._attributes[('undefer', self.group)] = True @@ -311,7 +311,7 @@ class NoLoader(AbstractRelationshipLoader): return new_execute, None, None log.class_logger(NoLoader) - + class LazyLoader(AbstractRelationshipLoader): """Strategize a relationship() that loads when first accessed.""" @@ -320,7 +320,7 @@ class LazyLoader(AbstractRelationshipLoader): self.__lazywhere, \ self.__bind_to_col, \ self._equated_columns = self._create_lazy_clause(self.parent_property) - + self.logger.info("%s lazy loading clause %s", self, self.__lazywhere) # determine if our "lazywhere" clause is the same as the mapper's @@ -332,19 +332,19 @@ class LazyLoader(AbstractRelationshipLoader): use_proxies=True, equivalents=self.mapper._equivalent_columns ) - + if self.use_get: for col in self._equated_columns.keys(): if col in self.mapper._equivalent_columns: for c in self.mapper._equivalent_columns[col]: self._equated_columns[c] = self._equated_columns[col] - + self.logger.info("%s will use query.get() to " "optimize instance loads" % self) def init_class_attribute(self, mapper): self.is_class_level = True - + # MANYTOONE currently only needs the # "old" value for delete-orphan # cascades. the required _SingleParentValidator @@ -372,7 +372,7 @@ class LazyLoader(AbstractRelationshipLoader): return self._lazy_none_clause( reverse_direction, adapt_source=adapt_source) - + if not reverse_direction: criterion, bind_to_col, rev = \ self.__lazywhere, \ @@ -391,10 +391,10 @@ class LazyLoader(AbstractRelationshipLoader): o = state.obj() # strong ref dict_ = attributes.instance_dict(o) - + # use the "committed state" only if we're in a flush # for this state. - + sess = sessionlib._state_session(state) if sess is not None and sess._flushing: def visit_bindparam(bindparam): @@ -407,8 +407,8 @@ class LazyLoader(AbstractRelationshipLoader): if bindparam.key in bind_to_col: bindparam.callable = lambda: mapper._get_state_attr_by_column( state, dict_, bind_to_col[bindparam.key]) - - + + if self.parent_property.secondary is not None and alias_secondary: criterion = sql_util.ClauseAdapter( self.parent_property.secondary.alias()).\ @@ -420,7 +420,7 @@ class LazyLoader(AbstractRelationshipLoader): if adapt_source: criterion = adapt_source(criterion) return criterion - + def _lazy_none_clause(self, reverse_direction=False, adapt_source=None): if not reverse_direction: criterion, bind_to_col, rev = \ @@ -438,18 +438,18 @@ class LazyLoader(AbstractRelationshipLoader): if adapt_source: criterion = adapt_source(criterion) return criterion - + def _load_for_state(self, state, passive): if not state.key and \ (not self.parent_property.load_on_pending or not state.session_id): return attributes.ATTR_EMPTY - + instance_mapper = state.manager.mapper prop = self.parent_property key = self.key prop_mapper = self.mapper pending = not state.key - + if ( passive is attributes.PASSIVE_NO_FETCH and not self.use_get @@ -458,7 +458,7 @@ class LazyLoader(AbstractRelationshipLoader): pending ): return attributes.PASSIVE_NO_RESULT - + session = sessionlib._state_session(state) if not session: raise orm_exc.DetachedInstanceError( @@ -474,7 +474,7 @@ class LazyLoader(AbstractRelationshipLoader): get_attr = instance_mapper._get_committed_state_attr_by_column else: get_attr = instance_mapper._get_state_attr_by_column - + dict_ = state.dict ident = [ get_attr( @@ -486,23 +486,23 @@ class LazyLoader(AbstractRelationshipLoader): ] if attributes.PASSIVE_NO_RESULT in ident: return attributes.PASSIVE_NO_RESULT - + if _none_set.issuperset(ident): return None - + ident_key = prop_mapper.identity_key_from_primary_key(ident) instance = Query._get_from_identity(session, ident_key, passive) if instance is not None: return instance elif passive is attributes.PASSIVE_NO_FETCH: return attributes.PASSIVE_NO_RESULT - + q = session.query(prop_mapper)._adapt_all_clauses() - + # don't autoflush on pending if pending: q = q.autoflush(False) - + if state.load_path: q = q._with_current_path(state.load_path + (key,)) @@ -524,12 +524,12 @@ class LazyLoader(AbstractRelationshipLoader): q = q.options(EagerLazyOption((rev.key,), lazy='select')) lazy_clause = self.lazy_clause(state) - + if pending: bind_values = sql_util.bind_values(lazy_clause) if None in bind_values: return None - + q = q.filter(lazy_clause) result = q.all() @@ -543,7 +543,7 @@ class LazyLoader(AbstractRelationshipLoader): "Multiple rows returned with " "uselist=False for lazily-loaded attribute '%s' " % prop) - + return result[0] else: return None @@ -568,14 +568,14 @@ class LazyLoader(AbstractRelationshipLoader): # this class - reset its # per-instance attribute state, so that the class-level # lazy loader is - # executed when next referenced on this instance. + # executed when next referenced on this instance. # this is needed in # populate_existing() types of scenarios to reset # any existing state. state.reset(dict_, key) return new_execute, None, None - + @classmethod def _create_lazy_clause(cls, prop, reverse_direction=False): binds = util.column_dict() @@ -592,7 +592,7 @@ class LazyLoader(AbstractRelationshipLoader): _list = lookup.setdefault(l, []) _list.append((l, r)) equated_columns[r] = l - + def col_to_bind(col): if col in lookup: for tobind, equated in lookup[col]: @@ -602,48 +602,48 @@ class LazyLoader(AbstractRelationshipLoader): binds[col] = sql.bindparam(None, None, type_=col.type) return binds[col] return None - + lazywhere = prop.primaryjoin if prop.secondaryjoin is None or not reverse_direction: lazywhere = visitors.replacement_traverse( lazywhere, {}, col_to_bind) - + if prop.secondaryjoin is not None: secondaryjoin = prop.secondaryjoin if reverse_direction: secondaryjoin = visitors.replacement_traverse( secondaryjoin, {}, col_to_bind) lazywhere = sql.and_(lazywhere, secondaryjoin) - + bind_to_col = dict((binds[col].key, col) for col in binds) - + return lazywhere, bind_to_col, equated_columns - + log.class_logger(LazyLoader) class LoadLazyAttribute(object): """serializable loader object used by LazyLoader""" - + def __init__(self, state, key): self.state = state self.key = key - + def __call__(self, passive=False): state, key = self.state, self.key instance_mapper = state.manager.mapper prop = instance_mapper._props[key] strategy = prop._strategies[LazyLoader] - + return strategy._load_for_state(state, passive) - + class ImmediateLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.parent_property.\ _get_strategy(LazyLoader).\ init_class_attribute(mapper) - + def setup_query(self, context, entity, path, reduced_path, adapter, column_collection=None, parentmapper=None, **kwargs): @@ -652,29 +652,29 @@ class ImmediateLoader(AbstractRelationshipLoader): def create_row_processor(self, context, path, reduced_path, mapper, row, adapter): def execute(state, dict_, row): state.get_impl(self.key).get(state, dict_) - + return None, None, execute - + class SubqueryLoader(AbstractRelationshipLoader): def init(self): super(SubqueryLoader, self).init() self.join_depth = self.parent_property.join_depth - + def init_class_attribute(self, mapper): self.parent_property.\ _get_strategy(LazyLoader).\ init_class_attribute(mapper) - + def setup_query(self, context, entity, path, reduced_path, adapter, column_collection=None, parentmapper=None, **kwargs): if not context.query._enable_eagerloads: return - + path = path + (self.key, ) reduced_path = reduced_path + (self.key, ) - + # build up a path indicating the path from the leftmost # entity to the thing we're subquery loading. subq_path = context.attributes.get(('subquery_path', None), ()) @@ -689,13 +689,13 @@ class SubqueryLoader(AbstractRelationshipLoader): else: if self.mapper.base_mapper in interfaces._reduce_path(subq_path): return - + orig_query = context.attributes.get( ("orig_query", SubqueryLoader), context.query) subq_mapper = mapperutil._class_to_mapper(subq_path[0]) - + # determine attributes of the leftmost mapper if self.parent.isa(subq_mapper) and self.key==subq_path[1]: leftmost_mapper, leftmost_prop = \ @@ -705,7 +705,7 @@ class SubqueryLoader(AbstractRelationshipLoader): subq_mapper, \ subq_mapper._props[subq_path[1]] leftmost_cols, remote_cols = self._local_remote_columns(leftmost_prop) - + leftmost_attr = [ leftmost_mapper._columntoproperty[c].class_attribute for c in leftmost_cols @@ -728,11 +728,11 @@ class SubqueryLoader(AbstractRelationshipLoader): # which we'll join onto. embed_q = q.with_labels().subquery() left_alias = mapperutil.AliasedClass(leftmost_mapper, embed_q) - + # q becomes a new query. basically doing a longhand # "from_self()". (from_self() itself not quite industrial # strength enough for all contingencies...but very close) - + q = q.session.query(self.mapper) q._attributes = { ("orig_query", SubqueryLoader): orig_query, @@ -760,25 +760,25 @@ class SubqueryLoader(AbstractRelationshipLoader): ] q = q.order_by(*local_attr) q = q.add_columns(*local_attr) - + for i, (mapper, key) in enumerate(to_join): - + # we need to use query.join() as opposed to # orm.join() here because of the # rich behavior it brings when dealing with # "with_polymorphic" mappers. "aliased" # and "from_joinpoint" take care of most of # the chaining and aliasing for us. - + first = i == 0 middle = i < len(to_join) - 1 second_to_last = i == len(to_join) - 2 - + if first: attr = getattr(left_alias, key) else: attr = key - + if second_to_last: q = q.join(parent_alias, attr, from_joinpoint=True) else: @@ -804,11 +804,11 @@ class SubqueryLoader(AbstractRelationshipLoader): ) ) q = q.order_by(*eager_order_by) - + # add new query to attributes to be picked up # by create_row_processor context.attributes[('subquery', reduced_path)] = q - + def _local_remote_columns(self, prop): if prop.secondary is None: return zip(*prop.local_remote_pairs) @@ -819,7 +819,7 @@ class SubqueryLoader(AbstractRelationshipLoader): p[0] for p in prop. secondary_synchronize_pairs ] - + def create_row_processor(self, context, path, reduced_path, mapper, row, adapter): if not self.parent.class_manager[self.key].impl.supports_population: @@ -827,30 +827,30 @@ class SubqueryLoader(AbstractRelationshipLoader): "'%s' does not support object " "population - eager loading cannot be applied." % self) - + reduced_path = reduced_path + (self.key,) - + if ('subquery', reduced_path) not in context.attributes: return None, None, None - + local_cols, remote_cols = self._local_remote_columns(self.parent_property) remote_attr = [ self.mapper._columntoproperty[c].key for c in remote_cols] - + q = context.attributes[('subquery', reduced_path)] - + collections = dict( (k, [v[0] for v in v]) for k, v in itertools.groupby( q, lambda x:x[1:] )) - + if adapter: local_cols = [adapter.columns[c] for c in local_cols] - + if self.uselist: def execute(state, dict_, row): collection = collections.get( @@ -870,11 +870,11 @@ class SubqueryLoader(AbstractRelationshipLoader): "Multiple rows returned with " "uselist=False for eagerly-loaded attribute '%s' " % self) - + scalar = collection[0] state.get_impl(self.key).\ set_committed_value(state, dict_, scalar) - + return execute, None, None log.class_logger(SubqueryLoader) @@ -882,7 +882,7 @@ log.class_logger(SubqueryLoader) class EagerLoader(AbstractRelationshipLoader): """Strategize a relationship() that loads within the process of the parent object being selected.""" - + def init(self): super(EagerLoader, self).init() self.join_depth = self.parent_property.join_depth @@ -890,27 +890,27 @@ class EagerLoader(AbstractRelationshipLoader): def init_class_attribute(self, mapper): self.parent_property.\ _get_strategy(LazyLoader).init_class_attribute(mapper) - + def setup_query(self, context, entity, path, reduced_path, adapter, \ column_collection=None, parentmapper=None, allow_innerjoin=True, **kwargs): """Add a left outer join to the statement thats being constructed.""" - + if not context.query._enable_eagerloads: return - + path = path + (self.key,) reduced_path = reduced_path + (self.key,) - + # check for user-defined eager alias if ("user_defined_eager_row_processor", reduced_path) in\ context.attributes: clauses = context.attributes[ ("user_defined_eager_row_processor", reduced_path)] - + adapter = entity._get_entity_clauses(context.query, context) if adapter and clauses: context.attributes[ @@ -920,9 +920,9 @@ class EagerLoader(AbstractRelationshipLoader): context.attributes[ ("user_defined_eager_row_processor", reduced_path)] = clauses = adapter - + add_to_collection = context.primary_columns - + else: # check for join_depth or basic recursion, # if the current path was not explicitly stated as @@ -950,7 +950,7 @@ class EagerLoader(AbstractRelationshipLoader): # if this is an outer join, all eager joins from # here must also be outer joins allow_innerjoin = False - + context.create_eager_joins.append( (self._create_eager_join, context, entity, path, adapter, @@ -961,10 +961,10 @@ class EagerLoader(AbstractRelationshipLoader): context.attributes[ ("eager_row_processor", reduced_path) ] = clauses - + path += (self.mapper,) reduced_path += (self.mapper.base_mapper,) - + for value in self.mapper._polymorphic_properties: value.setup( context, @@ -975,22 +975,22 @@ class EagerLoader(AbstractRelationshipLoader): parentmapper=self.mapper, column_collection=add_to_collection, allow_innerjoin=allow_innerjoin) - + def _create_eager_join(self, context, entity, path, adapter, parentmapper, clauses, innerjoin): - + if parentmapper is None: localparent = entity.mapper else: localparent = parentmapper - + # whether or not the Query will wrap the selectable in a subquery, # and then attach eager load joins to that (i.e., in the case of # LIMIT/OFFSET etc.) should_nest_selectable = context.multi_row_eager_loaders and \ context.query._should_nest_selectable - + entity_key = None if entity not in context.eager_joins and \ not should_nest_selectable and \ @@ -1024,7 +1024,7 @@ class EagerLoader(AbstractRelationshipLoader): ), self.key, self.parent_property ) - + if onclause is self.parent_property: # TODO: this is a temporary hack to # account for polymorphic eager loads where @@ -1051,7 +1051,7 @@ class EagerLoader(AbstractRelationshipLoader): # ensure all the parent cols in the primaryjoin are actually # in the # columns clause (i.e. are not deferred), so that aliasing applied - # by the Query propagates those columns outward. + # by the Query propagates those columns outward. # This has the effect # of "undefering" those columns. for col in sql_util.find_columns( @@ -1060,7 +1060,7 @@ class EagerLoader(AbstractRelationshipLoader): if adapter: col = adapter.columns[col] context.primary_columns.append(col) - + if self.parent_property.order_by: context.eager_order_by += \ eagerjoin._target_adapter.\ @@ -1070,7 +1070,7 @@ class EagerLoader(AbstractRelationshipLoader): ) ) - + def _create_eager_adapter(self, context, row, adapter, path, reduced_path): if ("user_defined_eager_row_processor", reduced_path) in \ context.attributes: @@ -1107,13 +1107,13 @@ class EagerLoader(AbstractRelationshipLoader): our_path = path + (self.key,) our_reduced_path = reduced_path + (self.key,) - + eager_adapter = self._create_eager_adapter( context, row, adapter, our_path, our_reduced_path) - + if eager_adapter is not False: key = self.key _instance = self.mapper._instance_processor( @@ -1121,7 +1121,7 @@ class EagerLoader(AbstractRelationshipLoader): our_path + (self.mapper,), our_reduced_path + (self.mapper.base_mapper,), eager_adapter) - + if not self.uselist: def new_execute(state, dict_, row): # set a scalar object instance directly on the parent @@ -1184,11 +1184,11 @@ class EagerLazyOption(StrategizedOption): self.chained = chained self.propagate_to_loaders = propagate_to_loaders self.strategy_cls = factory(lazy) - + @property def is_eager(self): return self.lazy in (False, 'joined', 'subquery') - + @property def is_chained(self): return self.is_eager and self.chained @@ -1209,16 +1209,16 @@ def factory(identifier): return ImmediateLoader else: return LazyLoader - - - + + + class EagerJoinOption(PropertyOption): - + def __init__(self, key, innerjoin, chained=False): super(EagerJoinOption, self).__init__(key) self.innerjoin = innerjoin self.chained = chained - + def is_chained(self): return self.chained @@ -1228,9 +1228,9 @@ class EagerJoinOption(PropertyOption): query._attributes[("eager_join_type", path)] = self.innerjoin else: query._attributes[("eager_join_type", paths[-1])] = self.innerjoin - + class LoadEagerFromAliasOption(PropertyOption): - + def __init__(self, key, alias=None): super(LoadEagerFromAliasOption, self).__init__(key) if alias is not None: @@ -1270,13 +1270,13 @@ def single_parent_validator(desc, prop): (mapperutil.instance_str(value), state.class_, prop) ) return value - + def append(state, value, initiator): return _do_check(state, value, None, initiator) def set_(state, value, oldvalue, initiator): return _do_check(state, value, oldvalue, initiator) - + event.listen(desc, 'append', append, raw=True, retval=True, active_history=True) event.listen(desc, 'set', set_, raw=True, retval=True, active_history=True) - + diff --git a/lib/sqlalchemy/orm/sync.py b/lib/sqlalchemy/orm/sync.py index 30d56d168..bc250b226 100644 --- a/lib/sqlalchemy/orm/sync.py +++ b/lib/sqlalchemy/orm/sync.py @@ -14,7 +14,7 @@ def populate(source, source_mapper, dest, dest_mapper, synchronize_pairs, uowcommit, flag_cascaded_pks): source_dict = source.dict dest_dict = dest.dict - + for l, r in synchronize_pairs: try: # inline of source_mapper._get_state_attr_by_column @@ -29,7 +29,7 @@ def populate(source, source_mapper, dest, dest_mapper, dest.manager[prop.key].impl.set(dest, dest_dict, value, None) except exc.UnmappedColumnError: _raise_col_to_prop(True, source_mapper, l, dest_mapper, r) - + # techically the "r.primary_key" check isn't # needed here, but we check for this condition to limit # how often this logic is invoked for memory/performance @@ -75,7 +75,7 @@ def populate_dict(source, source_mapper, dict_, synchronize_pairs): def source_modified(uowcommit, source, source_mapper, synchronize_pairs): """return true if the source object has changes from an old to a new value on the given synchronize pairs - + """ for l, r in synchronize_pairs: try: diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index e6b1c0483..f1c5fcfc6 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -21,10 +21,10 @@ session = util.importlater("sqlalchemy.orm", "session") def track_cascade_events(descriptor, prop): """Establish event listeners on object attributes which handle cascade-on-set/append. - + """ key = prop.key - + def append(state, item, initiator): # process "save_update" cascade rules for when # an instance is appended to the list of another instance @@ -38,7 +38,7 @@ def track_cascade_events(descriptor, prop): not sess._contains_state(item_state): sess._save_or_update_state(item_state) return item - + def remove(state, item, initiator): sess = session._state_session(state) if sess: @@ -65,15 +65,15 @@ def track_cascade_events(descriptor, prop): (prop.cascade_backrefs or key == initiator.key) and \ not sess._contains_state(newvalue_state): sess._save_or_update_state(newvalue_state) - + if oldvalue is not None and prop.cascade.delete_orphan: oldvalue_state = attributes.instance_state(oldvalue) - + if oldvalue_state in sess._new and \ prop.mapper._is_orphan(oldvalue_state): sess.expunge(oldvalue) return newvalue - + event.listen(descriptor, 'append', append, raw=True, retval=True) event.listen(descriptor, 'remove', remove, raw=True, retval=True) event.listen(descriptor, 'set', set_, raw=True, retval=True) @@ -86,45 +86,45 @@ class UOWTransaction(object): # dictionary used by external actors to # store arbitrary state information. self.attributes = {} - + # dictionary of mappers to sets of # DependencyProcessors, which are also # set to be part of the sorted flush actions, # which have that mapper as a parent. self.deps = util.defaultdict(set) - + # dictionary of mappers to sets of InstanceState # items pending for flush which have that mapper # as a parent. self.mappers = util.defaultdict(set) - + # a dictionary of Preprocess objects, which gather # additional states impacted by the flush # and determine if a flush action is needed self.presort_actions = {} - + # dictionary of PostSortRec objects, each # one issues work during the flush within # a certain ordering. self.postsort_actions = {} - + # a set of 2-tuples, each containing two # PostSortRec objects where the second # is dependent on the first being executed # first self.dependencies = set() - + # dictionary of InstanceState-> (isdelete, listonly) # tuples, indicating if this state is to be deleted # or insert/updated, or just refreshed self.states = {} - + # tracks InstanceStates which will be receiving # a "post update" call. Keys are mappers, # values are a set of states and a set of the # columns which should be included in the update. self.post_update_states = util.defaultdict(lambda: (set(), set())) - + @property def has_work(self): return bool(self.states) @@ -132,23 +132,23 @@ class UOWTransaction(object): def is_deleted(self, state): """return true if the given state is marked as deleted within this uowtransaction.""" - + return state in self.states and self.states[state][0] - + def memo(self, key, callable_): if key in self.attributes: return self.attributes[key] else: self.attributes[key] = ret = callable_() return ret - + def remove_state_actions(self, state): """remove pending actions for a state from the uowtransaction.""" - + isdelete = self.states[state][0] - + self.states[state] = (isdelete, True) - + def get_attribute_history(self, state, key, passive=True): """facade to attributes.get_state_history(), including caching of results.""" @@ -157,7 +157,7 @@ class UOWTransaction(object): # cache the objects, not the states; the strong reference here # prevents newly loaded objects from being dereferenced during the # flush process - + if hashkey in self.attributes: history, state_history, cached_passive = self.attributes[hashkey] # if the cached lookup was "passive" and now @@ -180,17 +180,17 @@ class UOWTransaction(object): else: state_history = history self.attributes[hashkey] = (history, state_history, passive) - + return state_history - + def has_dep(self, processor): return (processor, True) in self.presort_actions - + def register_preprocessor(self, processor, fromparent): key = (processor, fromparent) if key not in self.presort_actions: self.presort_actions[key] = Preprocess(processor, fromparent) - + def register_object(self, state, isdelete=False, listonly=False, cancel_delete=False, operation=None, prop=None): @@ -203,56 +203,56 @@ class UOWTransaction(object): if state not in self.states: mapper = state.manager.mapper - + if mapper not in self.mappers: mapper._per_mapper_flush_actions(self) - + self.mappers[mapper].add(state) self.states[state] = (isdelete, listonly) else: if not listonly and (isdelete or cancel_delete): self.states[state] = (isdelete, False) return True - + def issue_post_update(self, state, post_update_cols): mapper = state.manager.mapper.base_mapper states, cols = self.post_update_states[mapper] states.add(state) cols.update(post_update_cols) - + @util.memoized_property def _mapper_for_dep(self): """return a dynamic mapping of (Mapper, DependencyProcessor) to True or False, indicating if the DependencyProcessor operates on objects of that Mapper. - + The result is stored in the dictionary persistently once calculated. - + """ return util.PopulateDict( lambda tup:tup[0]._props.get(tup[1].key) is tup[1].prop ) - + def filter_states_for_dep(self, dep, states): """Filter the given list of InstanceStates to those relevant to the given DependencyProcessor. - + """ mapper_for_dep = self._mapper_for_dep return [s for s in states if mapper_for_dep[(s.manager.mapper, dep)]] - + def states_for_mapper_hierarchy(self, mapper, isdelete, listonly): checktup = (isdelete, listonly) for mapper in mapper.base_mapper.self_and_descendants: for state in self.mappers[mapper]: if self.states[state] == checktup: yield state - + def _generate_actions(self): """Generate the full, unsorted collection of PostSortRecs as well as dependency pairs for this UOWTransaction. - + """ # execute presort_actions, until all states # have been processed. a presort_action might @@ -269,7 +269,7 @@ class UOWTransaction(object): self.cycles = cycles = topological.find_cycles( self.dependencies, self.postsort_actions.values()) - + if cycles: # if yes, break the per-mapper actions into # per-state actions @@ -294,7 +294,7 @@ class UOWTransaction(object): self.dependencies.remove(edge) for dep in convert[edge[1]]: self.dependencies.add((edge[0], dep)) - + return set([a for a in self.postsort_actions.values() if not a.disabled ] @@ -302,13 +302,13 @@ class UOWTransaction(object): def execute(self): postsort_actions = self._generate_actions() - + #sort = topological.sort(self.dependencies, postsort_actions) #print "--------------" #print self.dependencies #print list(sort) #print "COUNT OF POSTSORT ACTIONS", len(postsort_actions) - + # execute if self.cycles: for set_ in topological.sort_as_subsets( @@ -322,14 +322,14 @@ class UOWTransaction(object): self.dependencies, postsort_actions): rec.execute(self) - - + + def finalize_flush_changes(self): """mark processed objects as clean / deleted after a successful flush(). this method is called within the flush() method after the execute() method has succeeded and the transaction has been committed. - + """ for state, (isdelete, listonly) in self.states.iteritems(): if isdelete: @@ -348,18 +348,18 @@ class IterateMappersMixin(object): ) else: return self.dependency_processor.mapper.self_and_descendants - + class Preprocess(IterateMappersMixin): def __init__(self, dependency_processor, fromparent): self.dependency_processor = dependency_processor self.fromparent = fromparent self.processed = set() self.setup_flush_actions = False - + def execute(self, uow): delete_states = set() save_states = set() - + for mapper in self._mappers(uow): for state in uow.mappers[mapper].difference(self.processed): (isdelete, listonly) = uow.states[state] @@ -375,7 +375,7 @@ class Preprocess(IterateMappersMixin): if save_states: self.dependency_processor.presort_saves(uow, save_states) self.processed.update(save_states) - + if (delete_states or save_states): if not self.setup_flush_actions and ( self.dependency_processor.\ @@ -391,7 +391,7 @@ class Preprocess(IterateMappersMixin): class PostSortRec(object): disabled = False - + def __new__(cls, uow, *args): key = (cls, ) + args if key in uow.postsort_actions: @@ -401,10 +401,10 @@ class PostSortRec(object): ret = \ object.__new__(cls) return ret - + def execute_aggregate(self, uow, recs): self.execute(uow) - + def __repr__(self): return "%s(%s)" % ( self.__class__.__name__, @@ -417,7 +417,7 @@ class ProcessAll(IterateMappersMixin, PostSortRec): self.delete = delete self.fromparent = fromparent uow.deps[dependency_processor.parent.base_mapper].add(dependency_processor) - + def execute(self, uow): states = self._elements(uow) if self.delete: @@ -454,20 +454,20 @@ class IssuePostUpdate(PostSortRec): def execute(self, uow): states, cols = uow.post_update_states[self.mapper] states = [s for s in states if uow.states[s][0] == self.isdelete] - + self.mapper._post_update(states, uow, cols) class SaveUpdateAll(PostSortRec): def __init__(self, uow, mapper): self.mapper = mapper assert mapper is mapper.base_mapper - + def execute(self, uow): self.mapper._save_obj( uow.states_for_mapper_hierarchy(self.mapper, False, False), uow ) - + def per_state_flush_actions(self, uow): states = list(uow.states_for_mapper_hierarchy(self.mapper, False, False)) for rec in self.mapper._per_state_flush_actions( @@ -475,11 +475,11 @@ class SaveUpdateAll(PostSortRec): states, False): yield rec - + for dep in uow.deps[self.mapper]: states_for_prop = uow.filter_states_for_dep(dep, states) dep.per_state_flush_actions(uow, states_for_prop, False) - + class DeleteAll(PostSortRec): def __init__(self, uow, mapper): self.mapper = mapper @@ -498,7 +498,7 @@ class DeleteAll(PostSortRec): states, True): yield rec - + for dep in uow.deps[self.mapper]: states_for_prop = uow.filter_states_for_dep(dep, states) dep.per_state_flush_actions(uow, states_for_prop, True) @@ -531,12 +531,12 @@ class ProcessState(PostSortRec): mapperutil.state_str(self.state), self.delete ) - + class SaveUpdateState(PostSortRec): def __init__(self, uow, state, mapper): self.state = state self.mapper = mapper - + def execute_aggregate(self, uow, recs): cls_ = self.__class__ mapper = self.mapper @@ -559,7 +559,7 @@ class DeleteState(PostSortRec): def __init__(self, uow, state, mapper): self.state = state self.mapper = mapper - + def execute_aggregate(self, uow, recs): cls_ = self.__class__ mapper = self.mapper diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index a69670c29..7866aab2b 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -28,7 +28,7 @@ class CascadeOptions(dict): values = set() else: values = set(c.strip() for c in arg.split(',')) - + for name in ['save-update', 'delete', 'refresh-expire', 'merge', 'expunge']: boolean = name in values or 'all' in values @@ -38,7 +38,7 @@ class CascadeOptions(dict): self.delete_orphan = "delete-orphan" in values if self.delete_orphan: self['delete-orphan'] = True - + if self.delete_orphan and not self.delete: util.warn("The 'delete-orphan' cascade option requires " "'delete'. This will raise an error in 0.6.") @@ -61,10 +61,10 @@ def _validator_events(desc, key, validator): def set_(state, value, oldvalue, initiator): return validator(state.obj(), key, value) - + event.listen(desc, 'append', append, raw=True, retval=True) event.listen(desc, 'set', set_, raw=True, retval=True) - + def polymorphic_union(table_map, typecolname, aliasname='p_union'): """Create a ``UNION`` statement used by a polymorphic mapper. @@ -197,8 +197,8 @@ class AliasedClass(object): The ORM equivalent of a :func:`sqlalchemy.sql.expression.alias` construct, this object mimics the mapped class using a __getattr__ scheme and maintains a reference to a - real :class:`~sqlalchemy.sql.expression.Alias` object. - + real :class:`~sqlalchemy.sql.expression.Alias` object. + Usage is via the :class:`~sqlalchemy.orm.aliased()` synonym:: # find all pairs of users with the same name @@ -264,7 +264,7 @@ class AliasedClass(object): break else: raise AttributeError(key) - + if isinstance(attr, attributes.QueryableAttribute): return self.__adapt_prop(attr, key) elif hasattr(attr, 'func_code'): @@ -391,19 +391,19 @@ def with_parent(instance, prop): """Create filtering criterion that relates this query's primary entity to the given related instance, using established :func:`.relationship()` configuration. - + The SQL rendered is the same as that rendered when a lazy loader would fire off from the given parent on that attribute, meaning that the appropriate state is taken from the parent object in Python without the need to render joins to the parent table in the rendered statement. - + As of 0.6.4, this method accepts parent instances in all persistence states, including transient, persistent, and detached. Only the requisite primary key/foreign key attributes need to be populated. Previous versions didn't work with transient instances. - + :param instance: An instance which has some :func:`.relationship`. @@ -411,7 +411,7 @@ def with_parent(instance, prop): String property name, or class-bound attribute, which indicates what relationship from the instance should be used to reconcile the parent/child relationship. - + """ if isinstance(prop, basestring): mapper = object_mapper(instance) @@ -440,24 +440,24 @@ def _entity_info(entity, compile=True): if isinstance(entity, mapperlib.Mapper): mapper = entity - + elif isinstance(entity, type): class_manager = attributes.manager_of_class(entity) - + if class_manager is None: return None, entity, False - + mapper = class_manager.mapper else: return None, entity, False - + if compile and mapperlib.module._new_mappers: mapperlib.configure_mappers() return mapper, mapper._with_polymorphic_selectable, False def _entity_descriptor(entity, key): """Return a class attribute given an entity and string name. - + May return :class:`.InstrumentedAttribute` or user-defined attribute. @@ -516,7 +516,7 @@ def class_mapper(class_, compile=True): Raises UnmappedClassError if no mapping is configured. """ - + try: class_manager = attributes.manager_of_class(class_) mapper = class_manager.mapper @@ -542,7 +542,7 @@ def _class_to_mapper(class_or_mapper, compile=True): mapper = class_or_mapper else: raise exc.UnmappedClassError(class_or_mapper) - + if compile and mapperlib.module._new_mappers: mapperlib.configure_mappers() return mapper @@ -581,7 +581,7 @@ def state_class_str(state): return "None" else: return '<%s>' % (state.class_.__name__, ) - + def attribute_str(instance, attribute): return instance_str(instance) + "." + attribute |
