aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/collections.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/collections.py')
-rwxr-xr-xlib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/collections.py1473
1 files changed, 0 insertions, 1473 deletions
diff --git a/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/collections.py b/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/collections.py
deleted file mode 100755
index 14251920..00000000
--- a/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/collections.py
+++ /dev/null
@@ -1,1473 +0,0 @@
-# orm/collections.py
-# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-
-"""Support for collections of mapped entities.
-
-The collections package supplies the machinery used to inform the ORM of
-collection membership changes. An instrumentation via decoration approach is
-used, allowing arbitrary types (including built-ins) to be used as entity
-collections without requiring inheritance from a base class.
-
-Instrumentation decoration relays membership change events to the
-``InstrumentedCollectionAttribute`` that is currently managing the collection.
-The decorators observe function call arguments and return values, tracking
-entities entering or leaving the collection. Two decorator approaches are
-provided. One is a bundle of generic decorators that map function arguments
-and return values to events::
-
- from sqlalchemy.orm.collections import collection
- class MyClass(object):
- # ...
-
- @collection.adds(1)
- def store(self, item):
- self.data.append(item)
-
- @collection.removes_return()
- def pop(self):
- return self.data.pop()
-
-
-The second approach is a bundle of targeted decorators that wrap appropriate
-append and remove notifiers around the mutation methods present in the
-standard Python ``list``, ``set`` and ``dict`` interfaces. These could be
-specified in terms of generic decorator recipes, but are instead hand-tooled
-for increased efficiency. The targeted decorators occasionally implement
-adapter-like behavior, such as mapping bulk-set methods (``extend``,
-``update``, ``__setslice__``, etc.) into the series of atomic mutation events
-that the ORM requires.
-
-The targeted decorators are used internally for automatic instrumentation of
-entity collection classes. Every collection class goes through a
-transformation process roughly like so:
-
-1. If the class is a built-in, substitute a trivial sub-class
-2. Is this class already instrumented?
-3. Add in generic decorators
-4. Sniff out the collection interface through duck-typing
-5. Add targeted decoration to any undecorated interface method
-
-This process modifies the class at runtime, decorating methods and adding some
-bookkeeping properties. This isn't possible (or desirable) for built-in
-classes like ``list``, so trivial sub-classes are substituted to hold
-decoration::
-
- class InstrumentedList(list):
- pass
-
-Collection classes can be specified in ``relationship(collection_class=)`` as
-types or a function that returns an instance. Collection classes are
-inspected and instrumented during the mapper compilation phase. The
-collection_class callable will be executed once to produce a specimen
-instance, and the type of that specimen will be instrumented. Functions that
-return built-in types like ``lists`` will be adapted to produce instrumented
-instances.
-
-When extending a known type like ``list``, additional decorations are not
-generally not needed. Odds are, the extension method will delegate to a
-method that's already instrumented. For example::
-
- class QueueIsh(list):
- def push(self, item):
- self.append(item)
- def shift(self):
- return self.pop(0)
-
-There's no need to decorate these methods. ``append`` and ``pop`` are already
-instrumented as part of the ``list`` interface. Decorating them would fire
-duplicate events, which should be avoided.
-
-The targeted decoration tries not to rely on other methods in the underlying
-collection class, but some are unavoidable. Many depend on 'read' methods
-being present to properly instrument a 'write', for example, ``__setitem__``
-needs ``__getitem__``. "Bulk" methods like ``update`` and ``extend`` may also
-reimplemented in terms of atomic appends and removes, so the ``extend``
-decoration will actually perform many ``append`` operations and not call the
-underlying method at all.
-
-Tight control over bulk operation and the firing of events is also possible by
-implementing the instrumentation internally in your methods. The basic
-instrumentation package works under the general assumption that collection
-mutation will not raise unusual exceptions. If you want to closely
-orchestrate append and remove events with exception management, internal
-instrumentation may be the answer. Within your method,
-``collection_adapter(self)`` will retrieve an object that you can use for
-explicit control over triggering append and remove events.
-
-The owning object and InstrumentedCollectionAttribute are also reachable
-through the adapter, allowing for some very sophisticated behavior.
-
-"""
-
-import copy
-import inspect
-import operator
-import sys
-import weakref
-
-from sqlalchemy.sql import expression
-from sqlalchemy import schema, util, exc as sa_exc
-
-
-__all__ = ['collection', 'collection_adapter',
- 'mapped_collection', 'column_mapped_collection',
- 'attribute_mapped_collection']
-
-__instrumentation_mutex = util.threading.Lock()
-
-
-def column_mapped_collection(mapping_spec):
- """A dictionary-based collection type with column-based keying.
-
- Returns a MappedCollection factory with a keying function generated
- from mapping_spec, which may be a Column or a sequence of Columns.
-
- The key value must be immutable for the lifetime of the object. You
- can not, for example, map on foreign key values if those key values will
- change during the session, i.e. from None to a database-assigned integer
- after a session flush.
-
- """
- from sqlalchemy.orm.util import _state_mapper
- from sqlalchemy.orm.attributes import instance_state
-
- cols = [expression._only_column_elements(q, "mapping_spec")
- for q in util.to_list(mapping_spec)]
- if len(cols) == 1:
- def keyfunc(value):
- state = instance_state(value)
- m = _state_mapper(state)
- return m._get_state_attr_by_column(state, state.dict, cols[0])
- else:
- mapping_spec = tuple(cols)
- def keyfunc(value):
- state = instance_state(value)
- m = _state_mapper(state)
- return tuple(m._get_state_attr_by_column(state, state.dict, c)
- for c in mapping_spec)
- return lambda: MappedCollection(keyfunc)
-
-def attribute_mapped_collection(attr_name):
- """A dictionary-based collection type with attribute-based keying.
-
- Returns a MappedCollection factory with a keying based on the
- 'attr_name' attribute of entities in the collection.
-
- The key value must be immutable for the lifetime of the object. You
- can not, for example, map on foreign key values if those key values will
- change during the session, i.e. from None to a database-assigned integer
- after a session flush.
-
- """
- return lambda: MappedCollection(operator.attrgetter(attr_name))
-
-
-def mapped_collection(keyfunc):
- """A dictionary-based collection type with arbitrary keying.
-
- Returns a MappedCollection factory with a keying function generated
- from keyfunc, a callable that takes an entity and returns a key value.
-
- The key value must be immutable for the lifetime of the object. You
- can not, for example, map on foreign key values if those key values will
- change during the session, i.e. from None to a database-assigned integer
- after a session flush.
-
- """
- return lambda: MappedCollection(keyfunc)
-
-class collection(object):
- """Decorators for entity collection classes.
-
- The decorators fall into two groups: annotations and interception recipes.
-
- The annotating decorators (appender, remover, iterator,
- internally_instrumented, link) indicate the method's purpose and take no
- arguments. They are not written with parens::
-
- @collection.appender
- def append(self, append): ...
-
- The recipe decorators all require parens, even those that take no
- arguments::
-
- @collection.adds('entity')
- def insert(self, position, entity): ...
-
- @collection.removes_return()
- def popitem(self): ...
-
- Decorators can be specified in long-hand for Python 2.3, or with
- the class-level dict attribute '__instrumentation__'- see the source
- for details.
-
- """
- # Bundled as a class solely for ease of use: packaging, doc strings,
- # importability.
-
- @staticmethod
- def appender(fn):
- """Tag the method as the collection appender.
-
- The appender method is called with one positional argument: the value
- to append. The method will be automatically decorated with 'adds(1)'
- if not already decorated::
-
- @collection.appender
- def add(self, append): ...
-
- # or, equivalently
- @collection.appender
- @collection.adds(1)
- def add(self, append): ...
-
- # for mapping type, an 'append' may kick out a previous value
- # that occupies that slot. consider d['a'] = 'foo'- any previous
- # value in d['a'] is discarded.
- @collection.appender
- @collection.replaces(1)
- def add(self, entity):
- key = some_key_func(entity)
- previous = None
- if key in self:
- previous = self[key]
- self[key] = entity
- return previous
-
- If the value to append is not allowed in the collection, you may
- raise an exception. Something to remember is that the appender
- will be called for each object mapped by a database query. If the
- database contains rows that violate your collection semantics, you
- will need to get creative to fix the problem, as access via the
- collection will not work.
-
- If the appender method is internally instrumented, you must also
- receive the keyword argument '_sa_initiator' and ensure its
- promulgation to collection events.
-
- """
- setattr(fn, '_sa_instrument_role', 'appender')
- return fn
-
- @staticmethod
- def remover(fn):
- """Tag the method as the collection remover.
-
- The remover method is called with one positional argument: the value
- to remove. The method will be automatically decorated with
- :meth:`removes_return` if not already decorated::
-
- @collection.remover
- def zap(self, entity): ...
-
- # or, equivalently
- @collection.remover
- @collection.removes_return()
- def zap(self, ): ...
-
- If the value to remove is not present in the collection, you may
- raise an exception or return None to ignore the error.
-
- If the remove method is internally instrumented, you must also
- receive the keyword argument '_sa_initiator' and ensure its
- promulgation to collection events.
-
- """
- setattr(fn, '_sa_instrument_role', 'remover')
- return fn
-
- @staticmethod
- def iterator(fn):
- """Tag the method as the collection remover.
-
- The iterator method is called with no arguments. It is expected to
- return an iterator over all collection members::
-
- @collection.iterator
- def __iter__(self): ...
-
- """
- setattr(fn, '_sa_instrument_role', 'iterator')
- return fn
-
- @staticmethod
- def internally_instrumented(fn):
- """Tag the method as instrumented.
-
- This tag will prevent any decoration from being applied to the method.
- Use this if you are orchestrating your own calls to :func:`.collection_adapter`
- in one of the basic SQLAlchemy interface methods, or to prevent
- an automatic ABC method decoration from wrapping your implementation::
-
- # normally an 'extend' method on a list-like class would be
- # automatically intercepted and re-implemented in terms of
- # SQLAlchemy events and append(). your implementation will
- # never be called, unless:
- @collection.internally_instrumented
- def extend(self, items): ...
-
- """
- setattr(fn, '_sa_instrumented', True)
- return fn
-
- @staticmethod
- def link(fn):
- """Tag the method as a the "linked to attribute" event handler.
-
- This optional event handler will be called when the collection class
- is linked to or unlinked from the InstrumentedAttribute. It is
- invoked immediately after the '_sa_adapter' property is set on
- the instance. A single argument is passed: the collection adapter
- that has been linked, or None if unlinking.
-
- """
- setattr(fn, '_sa_instrument_role', 'link')
- return fn
-
- @staticmethod
- def converter(fn):
- """Tag the method as the collection converter.
-
- This optional method will be called when a collection is being
- replaced entirely, as in::
-
- myobj.acollection = [newvalue1, newvalue2]
-
- The converter method will receive the object being assigned and should
- return an iterable of values suitable for use by the ``appender``
- method. A converter must not assign values or mutate the collection,
- it's sole job is to adapt the value the user provides into an iterable
- of values for the ORM's use.
-
- The default converter implementation will use duck-typing to do the
- conversion. A dict-like collection will be convert into an iterable
- of dictionary values, and other types will simply be iterated::
-
- @collection.converter
- def convert(self, other): ...
-
- If the duck-typing of the object does not match the type of this
- collection, a TypeError is raised.
-
- Supply an implementation of this method if you want to expand the
- range of possible types that can be assigned in bulk or perform
- validation on the values about to be assigned.
-
- """
- setattr(fn, '_sa_instrument_role', 'converter')
- return fn
-
- @staticmethod
- def adds(arg):
- """Mark the method as adding an entity to the collection.
-
- Adds "add to collection" handling to the method. The decorator
- argument indicates which method argument holds the SQLAlchemy-relevant
- value. Arguments can be specified positionally (i.e. integer) or by
- name::
-
- @collection.adds(1)
- def push(self, item): ...
-
- @collection.adds('entity')
- def do_stuff(self, thing, entity=None): ...
-
- """
- def decorator(fn):
- setattr(fn, '_sa_instrument_before', ('fire_append_event', arg))
- return fn
- return decorator
-
- @staticmethod
- def replaces(arg):
- """Mark the method as replacing an entity in the collection.
-
- Adds "add to collection" and "remove from collection" handling to
- the method. The decorator argument indicates which method argument
- holds the SQLAlchemy-relevant value to be added, and return value, if
- any will be considered the value to remove.
-
- Arguments can be specified positionally (i.e. integer) or by name::
-
- @collection.replaces(2)
- def __setitem__(self, index, item): ...
-
- """
- def decorator(fn):
- setattr(fn, '_sa_instrument_before', ('fire_append_event', arg))
- setattr(fn, '_sa_instrument_after', 'fire_remove_event')
- return fn
- return decorator
-
- @staticmethod
- def removes(arg):
- """Mark the method as removing an entity in the collection.
-
- Adds "remove from collection" handling to the method. The decorator
- argument indicates which method argument holds the SQLAlchemy-relevant
- value to be removed. Arguments can be specified positionally (i.e.
- integer) or by name::
-
- @collection.removes(1)
- def zap(self, item): ...
-
- For methods where the value to remove is not known at call-time, use
- collection.removes_return.
-
- """
- def decorator(fn):
- setattr(fn, '_sa_instrument_before', ('fire_remove_event', arg))
- return fn
- return decorator
-
- @staticmethod
- def removes_return():
- """Mark the method as removing an entity in the collection.
-
- Adds "remove from collection" handling to the method. The return value
- of the method, if any, is considered the value to remove. The method
- arguments are not inspected::
-
- @collection.removes_return()
- def pop(self): ...
-
- For methods where the value to remove is known at call-time, use
- collection.remove.
-
- """
- def decorator(fn):
- setattr(fn, '_sa_instrument_after', 'fire_remove_event')
- return fn
- return decorator
-
-
-# public instrumentation interface for 'internally instrumented'
-# implementations
-def collection_adapter(collection):
- """Fetch the :class:`.CollectionAdapter` for a collection."""
-
- return getattr(collection, '_sa_adapter', None)
-
-def collection_iter(collection):
- """Iterate over an object supporting the @iterator or __iter__ protocols.
-
- If the collection is an ORM collection, it need not be attached to an
- object to be iterable.
-
- """
- try:
- return getattr(collection, '_sa_iterator',
- getattr(collection, '__iter__'))()
- except AttributeError:
- raise TypeError("'%s' object is not iterable" %
- type(collection).__name__)
-
-
-class CollectionAdapter(object):
- """Bridges between the ORM and arbitrary Python collections.
-
- Proxies base-level collection operations (append, remove, iterate)
- to the underlying Python collection, and emits add/remove events for
- entities entering or leaving the collection.
-
- The ORM uses an CollectionAdapter exclusively for interaction with
- entity collections.
-
- 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."
- return self._data()
-
- @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)
- if hasattr(data, '_sa_on_link'):
- getattr(data, '_sa_on_link')(self)
-
- def unlink(self, data):
- """Unlink a collection from any adapter, and fire a link event."""
- setattr(data, '_sa_adapter', None)
- if hasattr(data, '_sa_on_link'):
- getattr(data, '_sa_on_link')(None)
-
- def adapt_like_to_iterable(self, obj):
- """Converts collection-compatible objects to an iterable of values.
-
- Can be passed any type of object, and if the underlying collection
- determines that it can be adapted into a stream of values it can
- use, returns an iterable of values suitable for append()ing.
-
- This method may raise TypeError or any other suitable exception
- if adaptation fails.
-
- If a converter implementation is not supplied on the collection,
- a default duck-typing-based implementation is used.
-
- """
- converter = getattr(self._data(), '_sa_converter', None)
- if converter is not None:
- return converter(obj)
-
- setting_type = util.duck_type_collection(obj)
- receiving_type = util.duck_type_collection(self._data())
-
- if obj is None or setting_type != receiving_type:
- given = obj is None and 'None' or obj.__class__.__name__
- if receiving_type is None:
- wanted = self._data().__class__.__name__
- else:
- wanted = receiving_type.__name__
-
- raise TypeError(
- "Incompatible collection type: %s is not %s-like" % (
- given, wanted))
-
- # If the object is an adapted collection, return the (iterable)
- # adapter.
- if getattr(obj, '_sa_adapter', None) is not None:
- return getattr(obj, '_sa_adapter')
- elif setting_type == dict:
- # Py3K
- #return obj.values()
- # Py2K
- return getattr(obj, 'itervalues', getattr(obj, 'values'))()
- # end Py2K
- else:
- return iter(obj)
-
- 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):
- """Add or restore an entity to the collection, firing no events."""
- getattr(self._data(), '_sa_appender')(item, _sa_initiator=False)
-
- def append_multiple_without_event(self, items):
- """Add or restore an entity to the collection, firing no events."""
- appender = getattr(self._data(), '_sa_appender')
- for item in items:
- appender(item, _sa_initiator=False)
-
- def remove_with_event(self, item, initiator=None):
- """Remove an entity from the collection, firing mutation events."""
- getattr(self._data(), '_sa_remover')(item, _sa_initiator=initiator)
-
- def remove_without_event(self, item):
- """Remove an entity from the collection, firing no events."""
- getattr(self._data(), '_sa_remover')(item, _sa_initiator=False)
-
- 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)
-
- def clear_without_event(self):
- """Empty the collection, firing no events."""
-
- remover = getattr(self._data(), '_sa_remover')
- for item in list(self):
- remover(item, _sa_initiator=False)
-
- def __iter__(self):
- """Iterate over entities in the collection."""
-
- # Py3K requires iter() here
- return iter(getattr(self._data(), '_sa_iterator')())
-
- def __len__(self):
- """Count entities in the collection."""
- return len(list(getattr(self._data(), '_sa_iterator')()))
-
- def __nonzero__(self):
- return True
-
- def fire_append_event(self, item, initiator=None):
- """Notify that a entity has entered the collection.
-
- Initiator is a token owned by the InstrumentedAttribute that initiated the membership
- mutation, and should be left as None unless you are passing along
- an initiator value from a chained operation.
-
- """
- if initiator is not False and item is not None:
- return self.attr.fire_append_event(
- self.owner_state,
- self.owner_state.dict,
- item, initiator)
- else:
- return item
-
- def fire_remove_event(self, item, initiator=None):
- """Notify that a entity has been removed from the collection.
-
- Initiator is the InstrumentedAttribute that initiated the membership
- mutation, and should be left as None unless you are passing along
- an initiator value from a chained operation.
-
- """
- if initiator is not False and item is not None:
- self.attr.fire_remove_event(
- self.owner_state,
- self.owner_state.dict,
- item, initiator)
-
- def fire_pre_remove_event(self, initiator=None):
- """Notify that an entity is about to be removed from the collection.
-
- Only called if the entity cannot be removed after calling
- fire_remove_event().
-
- """
- self.attr.fire_pre_remove_event(
- self.owner_state,
- self.owner_state.dict,
- initiator=initiator)
-
- def __getstate__(self):
- return {'key': self._key,
- 'owner_state': self.owner_state,
- 'data': self.data}
-
- def __setstate__(self, d):
- self._key = d['key']
- self.owner_state = d['owner_state']
- self._data = weakref.ref(d['data'])
-
-
-def bulk_replace(values, existing_adapter, new_adapter):
- """Load a new collection, firing events based on prior like membership.
-
- Appends instances in ``values`` onto the ``new_adapter``. Events will be
- fired for any instance not present in the ``existing_adapter``. Any
- instances in ``existing_adapter`` not present in ``values`` will have
- remove events fired upon them.
-
- values
- An iterable of collection member instances
-
- existing_adapter
- A CollectionAdapter of instances to be replaced
-
- new_adapter
- An empty CollectionAdapter to load with ``values``
-
-
- """
- if not isinstance(values, list):
- values = list(values)
-
- idset = util.IdentitySet
- constants = idset(existing_adapter or ()).intersection(values or ())
- additions = idset(values or ()).difference(constants)
- removals = idset(existing_adapter or ()).difference(constants)
-
- for member in values or ():
- if member in additions:
- new_adapter.append_with_event(member)
- elif member in constants:
- new_adapter.append_without_event(member)
-
- if existing_adapter:
- for member in removals:
- existing_adapter.remove_with_event(member)
-
-def prepare_instrumentation(factory):
- """Prepare a callable for future use as a collection class factory.
-
- Given a collection class factory (either a type or no-arg callable),
- return another factory that will produce compatible instances when
- called.
-
- This function is responsible for converting collection_class=list
- into the run-time behavior of collection_class=InstrumentedList.
-
- """
- # Convert a builtin to 'Instrumented*'
- if factory in __canned_instrumentation:
- factory = __canned_instrumentation[factory]
-
- # Create a specimen
- cls = type(factory())
-
- # Did factory callable return a builtin?
- if cls in __canned_instrumentation:
- # Wrap it so that it returns our 'Instrumented*'
- factory = __converting_factory(factory)
- cls = factory()
-
- # Instrument the class if needed.
- if __instrumentation_mutex.acquire():
- try:
- if getattr(cls, '_sa_instrumented', None) != id(cls):
- _instrument_class(cls)
- finally:
- __instrumentation_mutex.release()
-
- return factory
-
-def __converting_factory(original_factory):
- """Convert the type returned by collection factories on the fly.
-
- Given a collection factory that returns a builtin type (e.g. a list),
- return a wrapped function that converts that type to one of our
- instrumented types.
-
- """
- def wrapper():
- collection = original_factory()
- type_ = type(collection)
- if type_ in __canned_instrumentation:
- # return an instrumented type initialized from the factory's
- # collection
- return __canned_instrumentation[type_](collection)
- else:
- raise sa_exc.InvalidRequestError(
- "Collection class factories must produce instances of a "
- "single class.")
- try:
- # often flawed but better than nothing
- wrapper.__name__ = "%sWrapper" % original_factory.__name__
- wrapper.__doc__ = original_factory.__doc__
- except:
- pass
- return wrapper
-
-def _instrument_class(cls):
- """Modify methods in a class and install instrumentation."""
-
- # TODO: more formally document this as a decoratorless/Python 2.3
- # option for specifying instrumentation. (likely doc'd here in code only,
- # not in online docs.) Useful for C types too.
- #
- # __instrumentation__ = {
- # 'rolename': 'methodname', # ...
- # 'methods': {
- # 'methodname': ('fire_{append,remove}_event', argspec,
- # 'fire_{append,remove}_event'),
- # 'append': ('fire_append_event', 1, None),
- # '__setitem__': ('fire_append_event', 1, 'fire_remove_event'),
- # 'pop': (None, None, 'fire_remove_event'),
- # }
- # }
-
- # In the normal call flow, a request for any of the 3 basic collection
- # types is transformed into one of our trivial subclasses
- # (e.g. InstrumentedList). Catch anything else that sneaks in here...
- if cls.__module__ == '__builtin__':
- raise sa_exc.ArgumentError(
- "Can not instrument a built-in type. Use a "
- "subclass, even a trivial one.")
-
- collection_type = util.duck_type_collection(cls)
- if collection_type in __interfaces:
- roles = __interfaces[collection_type].copy()
- decorators = roles.pop('_decorators', {})
- else:
- roles, decorators = {}, {}
-
- if hasattr(cls, '__instrumentation__'):
- roles.update(copy.deepcopy(getattr(cls, '__instrumentation__')))
-
- methods = roles.pop('methods', {})
-
- for name in dir(cls):
- method = getattr(cls, name, None)
- if not util.callable(method):
- continue
-
- # note role declarations
- if hasattr(method, '_sa_instrument_role'):
- role = method._sa_instrument_role
- assert role in ('appender', 'remover', 'iterator',
- 'link', 'converter')
- roles[role] = name
-
- # transfer instrumentation requests from decorated function
- # to the combined queue
- before, after = None, None
- if hasattr(method, '_sa_instrument_before'):
- op, argument = method._sa_instrument_before
- assert op in ('fire_append_event', 'fire_remove_event')
- before = op, argument
- if hasattr(method, '_sa_instrument_after'):
- op = method._sa_instrument_after
- assert op in ('fire_append_event', 'fire_remove_event')
- after = op
- if before:
- methods[name] = before[0], before[1], after
- elif after:
- methods[name] = None, None, after
-
- # apply ABC auto-decoration to methods that need it
- for method, decorator in decorators.items():
- fn = getattr(cls, method, None)
- if (fn and method not in methods and
- not hasattr(fn, '_sa_instrumented')):
- setattr(cls, method, decorator(fn))
-
- # ensure all roles are present, and apply implicit instrumentation if
- # needed
- if 'appender' not in roles or not hasattr(cls, roles['appender']):
- raise sa_exc.ArgumentError(
- "Type %s must elect an appender method to be "
- "a collection class" % cls.__name__)
- elif (roles['appender'] not in methods and
- not hasattr(getattr(cls, roles['appender']), '_sa_instrumented')):
- methods[roles['appender']] = ('fire_append_event', 1, None)
-
- if 'remover' not in roles or not hasattr(cls, roles['remover']):
- raise sa_exc.ArgumentError(
- "Type %s must elect a remover method to be "
- "a collection class" % cls.__name__)
- elif (roles['remover'] not in methods and
- not hasattr(getattr(cls, roles['remover']), '_sa_instrumented')):
- methods[roles['remover']] = ('fire_remove_event', 1, None)
-
- if 'iterator' not in roles or not hasattr(cls, roles['iterator']):
- raise sa_exc.ArgumentError(
- "Type %s must elect an iterator method to be "
- "a collection class" % cls.__name__)
-
- # apply ad-hoc instrumentation from decorators, class-level defaults
- # and implicit role declarations
- for method, (before, argument, after) in methods.items():
- setattr(cls, method,
- _instrument_membership_mutator(getattr(cls, method),
- before, argument, after))
- # intern the role map
- for role, method in roles.items():
- setattr(cls, '_sa_%s' % role, getattr(cls, method))
-
- setattr(cls, '_sa_instrumented', id(cls))
-
-def _instrument_membership_mutator(method, before, argument, after):
- """Route method args and/or return value through the collection adapter."""
- # This isn't smart enough to handle @adds(1) for 'def fn(self, (a, b))'
- if before:
- fn_args = list(util.flatten_iterator(inspect.getargspec(method)[0]))
- if type(argument) is int:
- pos_arg = argument
- named_arg = len(fn_args) > argument and fn_args[argument] or None
- else:
- if argument in fn_args:
- pos_arg = fn_args.index(argument)
- else:
- pos_arg = None
- named_arg = argument
- del fn_args
-
- def wrapper(*args, **kw):
- if before:
- if pos_arg is None:
- if named_arg not in kw:
- raise sa_exc.ArgumentError(
- "Missing argument %s" % argument)
- value = kw[named_arg]
- else:
- if len(args) > pos_arg:
- value = args[pos_arg]
- elif named_arg in kw:
- value = kw[named_arg]
- else:
- raise sa_exc.ArgumentError(
- "Missing argument %s" % argument)
-
- initiator = kw.pop('_sa_initiator', None)
- if initiator is False:
- executor = None
- else:
- executor = getattr(args[0], '_sa_adapter', None)
-
- if before and executor:
- getattr(executor, before)(value, initiator)
-
- if not after or not executor:
- return method(*args, **kw)
- else:
- res = method(*args, **kw)
- if res is not None:
- getattr(executor, after)(res, initiator)
- return res
- try:
- wrapper._sa_instrumented = True
- wrapper.__name__ = method.__name__
- wrapper.__doc__ = method.__doc__
- except:
- pass
- return wrapper
-
-def __set(collection, item, _sa_initiator=None):
- """Run set events, may eventually be inlined into decorators."""
-
- if _sa_initiator is not False and item is not None:
- executor = getattr(collection, '_sa_adapter', 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:
- executor = getattr(collection, '_sa_adapter', None)
- if executor:
- getattr(executor, 'fire_remove_event')(item, _sa_initiator)
-
-def __before_delete(collection, _sa_initiator=None):
- """Special method to run 'commit existing value' methods"""
- executor = getattr(collection, '_sa_adapter', None)
- if executor:
- getattr(executor, 'fire_pre_remove_event')(_sa_initiator)
-
-def _list_decorators():
- """Tailored instrumentation wrappers for any list-like class."""
-
- def _tidy(fn):
- setattr(fn, '_sa_instrumented', True)
- fn.__doc__ = getattr(getattr(list, fn.__name__), '__doc__')
-
- def append(fn):
- def append(self, item, _sa_initiator=None):
- item = __set(self, item, _sa_initiator)
- fn(self, item)
- _tidy(append)
- return append
-
- def remove(fn):
- def remove(self, value, _sa_initiator=None):
- __before_delete(self, _sa_initiator)
- # testlib.pragma exempt:__eq__
- fn(self, value)
- __del(self, value, _sa_initiator)
- _tidy(remove)
- return remove
-
- def insert(fn):
- def insert(self, index, value):
- value = __set(self, value)
- fn(self, index, value)
- _tidy(insert)
- return insert
-
- def __setitem__(fn):
- def __setitem__(self, index, value):
- if not isinstance(index, slice):
- existing = self[index]
- if existing is not None:
- __del(self, existing)
- value = __set(self, value)
- fn(self, index, value)
- else:
- # slice assignment requires __delitem__, insert, __len__
- step = index.step or 1
- start = index.start or 0
- if start < 0:
- start += len(self)
- 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:
- rng = range(start, stop, step)
- if len(value) != len(rng):
- raise ValueError(
- "attempt to assign sequence of size %s to "
- "extended slice of size %s" % (len(value),
- len(rng)))
- for i, item in zip(rng, value):
- self.__setitem__(i, item)
- _tidy(__setitem__)
- return __setitem__
-
- def __delitem__(fn):
- def __delitem__(self, index):
- if not isinstance(index, slice):
- item = self[index]
- __del(self, item)
- fn(self, index)
- else:
- # slice deletion requires __getslice__ and a slice-groking
- # __getitem__ for stepped deletion
- # note: not breaking this into atomic dels
- for item in self[index]:
- __del(self, item)
- fn(self, index)
- _tidy(__delitem__)
- return __delitem__
-
- # Py2K
- def __setslice__(fn):
- def __setslice__(self, start, end, values):
- for value in self[start:end]:
- __del(self, value)
- values = [__set(self, value) for value in values]
- fn(self, start, end, values)
- _tidy(__setslice__)
- return __setslice__
-
- def __delslice__(fn):
- def __delslice__(self, start, end):
- for value in self[start:end]:
- __del(self, value)
- fn(self, start, end)
- _tidy(__delslice__)
- return __delslice__
- # end Py2K
-
- def extend(fn):
- def extend(self, iterable):
- for value in iterable:
- self.append(value)
- _tidy(extend)
- return extend
-
- def __iadd__(fn):
- def __iadd__(self, iterable):
- # list.__iadd__ takes any iterable and seems to let TypeError raise
- # as-is instead of returning NotImplemented
- for value in iterable:
- self.append(value)
- return self
- _tidy(__iadd__)
- return __iadd__
-
- def pop(fn):
- def pop(self, index=-1):
- __before_delete(self)
- item = fn(self, index)
- __del(self, item)
- return item
- _tidy(pop)
- return pop
-
- # __imul__ : not wrapping this. all members of the collection are already
- # present, so no need to fire appends... wrapping it with an explicit
- # decorator is still possible, so events on *= can be had if they're
- # desired. hard to imagine a use case for __imul__, though.
-
- l = locals().copy()
- l.pop('_tidy')
- return l
-
-def _dict_decorators():
- """Tailored instrumentation wrappers for any dict-like mapping class."""
-
- def _tidy(fn):
- setattr(fn, '_sa_instrumented', True)
- fn.__doc__ = getattr(getattr(dict, fn.__name__), '__doc__')
-
- Unspecified = util.symbol('Unspecified')
-
- def __setitem__(fn):
- def __setitem__(self, key, value, _sa_initiator=None):
- if key in self:
- __del(self, self[key], _sa_initiator)
- value = __set(self, value, _sa_initiator)
- fn(self, key, value)
- _tidy(__setitem__)
- return __setitem__
-
- def __delitem__(fn):
- def __delitem__(self, key, _sa_initiator=None):
- if key in self:
- __del(self, self[key], _sa_initiator)
- fn(self, key)
- _tidy(__delitem__)
- return __delitem__
-
- def clear(fn):
- def clear(self):
- for key in self:
- __del(self, self[key])
- fn(self)
- _tidy(clear)
- return clear
-
- def pop(fn):
- def pop(self, key, default=Unspecified):
- if key in self:
- __del(self, self[key])
- if default is Unspecified:
- return fn(self, key)
- else:
- return fn(self, key, default)
- _tidy(pop)
- return pop
-
- def popitem(fn):
- def popitem(self):
- __before_delete(self)
- item = fn(self)
- __del(self, item[1])
- return item
- _tidy(popitem)
- return popitem
-
- def setdefault(fn):
- def setdefault(self, key, default=None):
- if key not in self:
- self.__setitem__(key, default)
- return default
- else:
- return self.__getitem__(key)
- _tidy(setdefault)
- return setdefault
-
- if sys.version_info < (2, 4):
- def update(fn):
- def update(self, other):
- for key in other.keys():
- if key not in self or self[key] is not other[key]:
- self[key] = other[key]
- _tidy(update)
- return update
- else:
- def update(fn):
- def update(self, __other=Unspecified, **kw):
- if __other is not Unspecified:
- if hasattr(__other, 'keys'):
- for key in __other.keys():
- if (key not in self or
- self[key] is not __other[key]):
- self[key] = __other[key]
- else:
- for key, value in __other:
- if key not in self or self[key] is not value:
- self[key] = value
- for key in kw:
- if key not in self or self[key] is not kw[key]:
- self[key] = kw[key]
- _tidy(update)
- return update
-
- l = locals().copy()
- l.pop('_tidy')
- l.pop('Unspecified')
- return l
-
-if util.py3k:
- _set_binop_bases = (set, frozenset)
-else:
- import sets
- _set_binop_bases = (set, frozenset, sets.BaseSet)
-
-def _set_binops_check_strict(self, obj):
- """Allow only set, frozenset and self.__class__-derived objects in binops."""
- return isinstance(obj, _set_binop_bases + (self.__class__,))
-
-def _set_binops_check_loose(self, obj):
- """Allow anything set-like to participate in set binops."""
- return (isinstance(obj, _set_binop_bases + (self.__class__,)) or
- util.duck_type_collection(obj) == set)
-
-
-def _set_decorators():
- """Tailored instrumentation wrappers for any set-like class."""
-
- def _tidy(fn):
- setattr(fn, '_sa_instrumented', True)
- fn.__doc__ = getattr(getattr(set, fn.__name__), '__doc__')
-
- Unspecified = util.symbol('Unspecified')
-
- def add(fn):
- def add(self, value, _sa_initiator=None):
- if value not in self:
- value = __set(self, value, _sa_initiator)
- # testlib.pragma exempt:__hash__
- fn(self, value)
- _tidy(add)
- return add
-
- if sys.version_info < (2, 4):
- def discard(fn):
- def discard(self, value, _sa_initiator=None):
- if value in self:
- self.remove(value, _sa_initiator)
- _tidy(discard)
- return discard
- else:
- def discard(fn):
- def discard(self, value, _sa_initiator=None):
- # testlib.pragma exempt:__hash__
- if value in self:
- __del(self, value, _sa_initiator)
- # testlib.pragma exempt:__hash__
- fn(self, value)
- _tidy(discard)
- return discard
-
- def remove(fn):
- def remove(self, value, _sa_initiator=None):
- # testlib.pragma exempt:__hash__
- if value in self:
- __del(self, value, _sa_initiator)
- # testlib.pragma exempt:__hash__
- fn(self, value)
- _tidy(remove)
- return remove
-
- def pop(fn):
- def pop(self):
- __before_delete(self)
- item = fn(self)
- __del(self, item)
- return item
- _tidy(pop)
- return pop
-
- def clear(fn):
- def clear(self):
- for item in list(self):
- self.remove(item)
- _tidy(clear)
- return clear
-
- def update(fn):
- def update(self, value):
- for item in value:
- self.add(item)
- _tidy(update)
- return update
-
- def __ior__(fn):
- def __ior__(self, value):
- if not _set_binops_check_strict(self, value):
- return NotImplemented
- for item in value:
- self.add(item)
- return self
- _tidy(__ior__)
- return __ior__
-
- def difference_update(fn):
- def difference_update(self, value):
- for item in value:
- self.discard(item)
- _tidy(difference_update)
- return difference_update
-
- def __isub__(fn):
- def __isub__(self, value):
- if not _set_binops_check_strict(self, value):
- return NotImplemented
- for item in value:
- self.discard(item)
- return self
- _tidy(__isub__)
- return __isub__
-
- def intersection_update(fn):
- def intersection_update(self, other):
- want, have = self.intersection(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
- _tidy(intersection_update)
- return intersection_update
-
- def __iand__(fn):
- def __iand__(self, other):
- if not _set_binops_check_strict(self, other):
- return NotImplemented
- want, have = self.intersection(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
- return self
- _tidy(__iand__)
- return __iand__
-
- def symmetric_difference_update(fn):
- def symmetric_difference_update(self, other):
- want, have = self.symmetric_difference(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
- _tidy(symmetric_difference_update)
- return symmetric_difference_update
-
- def __ixor__(fn):
- def __ixor__(self, other):
- if not _set_binops_check_strict(self, other):
- return NotImplemented
- want, have = self.symmetric_difference(other), set(self)
- remove, add = have - want, want - have
-
- for item in remove:
- self.remove(item)
- for item in add:
- self.add(item)
- return self
- _tidy(__ixor__)
- return __ixor__
-
- l = locals().copy()
- l.pop('_tidy')
- l.pop('Unspecified')
- return l
-
-
-class InstrumentedList(list):
- """An instrumented version of the built-in list."""
-
- __instrumentation__ = {
- 'appender': 'append',
- 'remover': 'remove',
- 'iterator': '__iter__', }
-
-class InstrumentedSet(set):
- """An instrumented version of the built-in set."""
-
- __instrumentation__ = {
- 'appender': 'add',
- 'remover': 'remove',
- 'iterator': '__iter__', }
-
-class InstrumentedDict(dict):
- """An instrumented version of the built-in dict."""
-
- # Py3K
- #__instrumentation__ = {
- # 'iterator': 'values', }
- # Py2K
- __instrumentation__ = {
- 'iterator': 'itervalues', }
- # end Py2K
-
-__canned_instrumentation = {
- list: InstrumentedList,
- set: InstrumentedSet,
- dict: InstrumentedDict,
- }
-
-__interfaces = {
- list: {'appender': 'append',
- 'remover': 'remove',
- 'iterator': '__iter__',
- '_decorators': _list_decorators(), },
- set: {'appender': 'add',
- 'remover': 'remove',
- 'iterator': '__iter__',
- '_decorators': _set_decorators(), },
- # decorators are required for dicts and object collections.
- # Py3K
- #dict: {'iterator': 'values',
- # '_decorators': _dict_decorators(), },
- # Py2K
- dict: {'iterator': 'itervalues',
- '_decorators': _dict_decorators(), },
- # end Py2K
- # < 0.4 compatible naming, deprecated- use decorators instead.
- None: {}
- }
-
-class MappedCollection(dict):
- """A basic dictionary-based collection class.
-
- Extends dict with the minimal bag semantics that collection classes require.
- ``set`` and ``remove`` are implemented in terms of a keying function: any
- callable that takes an object and returns an object for use as a dictionary
- key.
-
- """
-
- def __init__(self, keyfunc):
- """Create a new collection with keying provided by keyfunc.
-
- keyfunc may be any callable any callable that takes an object and
- returns an object for use as a dictionary key.
-
- The keyfunc will be called every time the ORM needs to add a member by
- value-only (such as when loading instances from the database) or
- remove a member. The usual cautions about dictionary keying apply-
- ``keyfunc(object)`` should return the same output for the life of the
- collection. Keying based on mutable properties can result in
- unreachable instances "lost" in the collection.
-
- """
- self.keyfunc = keyfunc
-
- def set(self, value, _sa_initiator=None):
- """Add an item by value, consulting the keyfunc for the key."""
-
- key = self.keyfunc(value)
- self.__setitem__(key, value, _sa_initiator)
- set = collection.internally_instrumented(set)
- set = collection.appender(set)
-
- def remove(self, value, _sa_initiator=None):
- """Remove an item by value, consulting the keyfunc for the key."""
-
- key = self.keyfunc(value)
- # Let self[key] raise if key is not in this collection
- # testlib.pragma exempt:__ne__
- if self[key] != value:
- raise sa_exc.InvalidRequestError(
- "Can not remove '%s': collection holds '%s' for key '%s'. "
- "Possible cause: is the MappedCollection key function "
- "based on mutable properties or properties that only obtain "
- "values after flush?" %
- (value, self[key], key))
- self.__delitem__(key, _sa_initiator)
- remove = collection.internally_instrumented(remove)
- remove = collection.remover(remove)
-
- def _convert(self, dictlike):
- """Validate and convert a dict-like object into values for set()ing.
-
- This is called behind the scenes when a MappedCollection is replaced
- entirely by another collection, as in::
-
- myobj.mappedcollection = {'a':obj1, 'b': obj2} # ...
-
- Raises a TypeError if the key in any (key, value) pair in the dictlike
- object does not match the key that this collection's keyfunc would
- have assigned for that value.
-
- """
- for incoming_key, value in util.dictlike_iteritems(dictlike):
- new_key = self.keyfunc(value)
- if incoming_key != new_key:
- raise TypeError(
- "Found incompatible key %r for value %r; this collection's "
- "keying function requires a key of %r for this value." % (
- incoming_key, value, new_key))
- yield value
- _convert = collection.converter(_convert)