~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/orm/collections.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski, Jakub Wilk, Piotr Ożarowski
  • Date: 2013-07-06 20:53:52 UTC
  • mfrom: (1.4.23) (16.1.17 experimental)
  • Revision ID: package-import@ubuntu.com-20130706205352-ryppl1eto3illd79
Tags: 0.8.2-1
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Piotr Ożarowski ]
* New upstream release
* Upload to unstable
* Build depend on python3-all instead of -dev, extensions are not built for
  Python 3.X 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# orm/collections.py
2
 
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
 
2
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
3
3
#
4
4
# This module is part of SQLAlchemy and is released under
5
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
12
12
collections without requiring inheritance from a base class.
13
13
 
14
14
Instrumentation decoration relays membership change events to the
15
 
``InstrumentedCollectionAttribute`` that is currently managing the collection.
 
15
:class:`.CollectionAttributeImpl` that is currently managing the collection.
16
16
The decorators observe function call arguments and return values, tracking
17
17
entities entering or leaving the collection.  Two decorator approaches are
18
18
provided.  One is a bundle of generic decorators that map function arguments
97
97
``collection_adapter(self)`` will retrieve an object that you can use for
98
98
explicit control over triggering append and remove events.
99
99
 
100
 
The owning object and InstrumentedCollectionAttribute are also reachable
 
100
The owning object and :class:`.CollectionAttributeImpl` are also reachable
101
101
through the adapter, allowing for some very sophisticated behavior.
102
102
 
103
103
"""
104
104
 
105
 
import copy
106
105
import inspect
107
106
import operator
108
 
import sys
109
107
import weakref
110
108
 
111
 
from sqlalchemy.sql import expression
112
 
from sqlalchemy import schema, util, exc as sa_exc
 
109
from ..sql import expression
 
110
from .. import util, exc as sa_exc
 
111
orm_util = util.importlater("sqlalchemy.orm", "util")
 
112
attributes = util.importlater("sqlalchemy.orm", "attributes")
 
113
 
113
114
 
114
115
__all__ = ['collection', 'collection_adapter',
115
116
           'mapped_collection', 'column_mapped_collection',
138
139
        return self.cols
139
140
 
140
141
    def __call__(self, value):
141
 
        state = instance_state(value)
142
 
        m = _state_mapper(state)
 
142
        state = attributes.instance_state(value)
 
143
        m = orm_util._state_mapper(state)
143
144
 
144
145
        key = [
145
146
            m._get_state_attr_by_column(state, state.dict, col)
151
152
        else:
152
153
            return key[0]
153
154
 
 
155
 
154
156
class _SerializableColumnGetter(object):
155
157
    """Column-based getter used in version 0.7.6 only.
156
158
 
160
162
    def __init__(self, colkeys):
161
163
        self.colkeys = colkeys
162
164
        self.composite = len(colkeys) > 1
 
165
 
163
166
    def __reduce__(self):
164
167
        return _SerializableColumnGetter, (self.colkeys,)
 
168
 
165
169
    def __call__(self, value):
166
 
        state = instance_state(value)
167
 
        m = _state_mapper(state)
 
170
        state = attributes.instance_state(value)
 
171
        m = orm_util._state_mapper(state)
168
172
        key = [m._get_state_attr_by_column(
169
173
                        state, state.dict,
170
174
                        m.mapped_table.columns[k])
174
178
        else:
175
179
            return key[0]
176
180
 
 
181
 
177
182
class _SerializableColumnGetterV2(_PlainColumnGetter):
178
183
    """Updated serializable getter which deals with
179
184
    multi-table mapped classes.
219
224
def column_mapped_collection(mapping_spec):
220
225
    """A dictionary-based collection type with column-based keying.
221
226
 
222
 
    Returns a :class:`.MappedCollection` factory with a keying function generated
223
 
    from mapping_spec, which may be a Column or a sequence of Columns.
 
227
    Returns a :class:`.MappedCollection` factory with a keying function
 
228
    generated from mapping_spec, which may be a Column or a sequence
 
229
    of Columns.
224
230
 
225
231
    The key value must be immutable for the lifetime of the object.  You
226
232
    can not, for example, map on foreign key values if those key values will
228
234
    after a session flush.
229
235
 
230
236
    """
231
 
    global _state_mapper, instance_state
232
 
    from sqlalchemy.orm.util import _state_mapper
233
 
    from sqlalchemy.orm.attributes import instance_state
234
 
 
235
237
    cols = [expression._only_column_elements(q, "mapping_spec")
236
238
                for q in util.to_list(mapping_spec)
237
239
            ]
238
240
    keyfunc = _PlainColumnGetter(cols)
239
241
    return lambda: MappedCollection(keyfunc)
240
242
 
 
243
 
241
244
class _SerializableAttrGetter(object):
242
245
    def __init__(self, name):
243
246
        self.name = name
249
252
    def __reduce__(self):
250
253
        return _SerializableAttrGetter, (self.name, )
251
254
 
 
255
 
252
256
def attribute_mapped_collection(attr_name):
253
257
    """A dictionary-based collection type with attribute-based keying.
254
258
 
269
273
def mapped_collection(keyfunc):
270
274
    """A dictionary-based collection type with arbitrary keying.
271
275
 
272
 
    Returns a :class:`.MappedCollection` factory with a keying function generated
273
 
    from keyfunc, a callable that takes an entity and returns a key value.
 
276
    Returns a :class:`.MappedCollection` factory with a keying function
 
277
    generated from keyfunc, a callable that takes an entity and returns a
 
278
    key value.
274
279
 
275
280
    The key value must be immutable for the lifetime of the object.  You
276
281
    can not, for example, map on foreign key values if those key values will
280
285
    """
281
286
    return lambda: MappedCollection(keyfunc)
282
287
 
 
288
 
283
289
class collection(object):
284
290
    """Decorators for entity collection classes.
285
291
 
286
292
    The decorators fall into two groups: annotations and interception recipes.
287
293
 
288
 
    The annotating decorators (appender, remover, iterator,
289
 
    internally_instrumented, link) indicate the method's purpose and take no
 
294
    The annotating decorators (appender, remover, iterator, linker, converter,
 
295
    internally_instrumented) indicate the method's purpose and take no
290
296
    arguments.  They are not written with parens::
291
297
 
292
298
        @collection.appender
394
400
    def internally_instrumented(fn):
395
401
        """Tag the method as instrumented.
396
402
 
397
 
        This tag will prevent any decoration from being applied to the method.
398
 
        Use this if you are orchestrating your own calls to :func:`.collection_adapter`
399
 
        in one of the basic SQLAlchemy interface methods, or to prevent
400
 
        an automatic ABC method decoration from wrapping your implementation::
 
403
        This tag will prevent any decoration from being applied to the
 
404
        method. Use this if you are orchestrating your own calls to
 
405
        :func:`.collection_adapter` in one of the basic SQLAlchemy
 
406
        interface methods, or to prevent an automatic ABC method
 
407
        decoration from wrapping your implementation::
401
408
 
402
409
            # normally an 'extend' method on a list-like class would be
403
410
            # automatically intercepted and re-implemented in terms of
411
418
        return fn
412
419
 
413
420
    @staticmethod
414
 
    def link(fn):
415
 
        """Tag the method as a the "linked to attribute" event handler.
 
421
    def linker(fn):
 
422
        """Tag the method as a "linked to attribute" event handler.
416
423
 
417
424
        This optional event handler will be called when the collection class
418
425
        is linked to or unlinked from the InstrumentedAttribute.  It is
421
428
        that has been linked, or None if unlinking.
422
429
 
423
430
        """
424
 
        setattr(fn, '_sa_instrument_role', 'link')
 
431
        setattr(fn, '_sa_instrument_role', 'linker')
425
432
        return fn
426
433
 
 
434
    link = linker
 
435
    """deprecated; synonym for :meth:`.collection.linker`."""
 
436
 
427
437
    @staticmethod
428
438
    def converter(fn):
429
439
        """Tag the method as the collection converter.
548
558
 
549
559
    return getattr(collection, '_sa_adapter', None)
550
560
 
 
561
 
551
562
def collection_iter(collection):
552
563
    """Iterate over an object supporting the @iterator or __iter__ protocols.
553
564
 
577
588
    of custom methods, such as to unwrap Zope security proxies.
578
589
 
579
590
    """
 
591
    invalidated = False
 
592
 
580
593
    def __init__(self, attr, owner_state, data):
581
594
        self._key = attr.key
582
595
        self._data = weakref.ref(data)
583
596
        self.owner_state = owner_state
584
597
        self.link_to_self(data)
585
598
 
 
599
    def _warn_invalidated(self):
 
600
        util.warn("This collection has been invalidated.")
 
601
 
586
602
    @property
587
603
    def data(self):
588
604
        "The entity collection being adapted."
595
611
    def link_to_self(self, data):
596
612
        """Link a collection to this adapter, and fire a link event."""
597
613
        setattr(data, '_sa_adapter', self)
598
 
        if hasattr(data, '_sa_on_link'):
599
 
            getattr(data, '_sa_on_link')(self)
 
614
        if hasattr(data, '_sa_linker'):
 
615
            getattr(data, '_sa_linker')(self)
600
616
 
601
617
    def unlink(self, data):
602
618
        """Unlink a collection from any adapter, and fire a link event."""
603
619
        setattr(data, '_sa_adapter', None)
604
 
        if hasattr(data, '_sa_on_link'):
605
 
            getattr(data, '_sa_on_link')(None)
 
620
        if hasattr(data, '_sa_linker'):
 
621
            getattr(data, '_sa_linker')(None)
606
622
 
607
623
    def adapt_like_to_iterable(self, obj):
608
624
        """Converts collection-compatible objects to an iterable of values.
702
718
    def fire_append_event(self, item, initiator=None):
703
719
        """Notify that a entity has entered the collection.
704
720
 
705
 
        Initiator is a token owned by the InstrumentedAttribute that initiated the membership
706
 
        mutation, and should be left as None unless you are passing along
707
 
        an initiator value from a chained operation.
 
721
        Initiator is a token owned by the InstrumentedAttribute that
 
722
        initiated the membership mutation, and should be left as None
 
723
        unless you are passing along an initiator value from a chained
 
724
        operation.
708
725
 
709
726
        """
710
 
        if initiator is not False and item is not None:
 
727
        if initiator is not False:
 
728
            if self.invalidated:
 
729
                self._warn_invalidated()
711
730
            return self.attr.fire_append_event(
712
731
                                    self.owner_state,
713
732
                                    self.owner_state.dict,
723
742
        an initiator value from a chained operation.
724
743
 
725
744
        """
726
 
        if initiator is not False and item is not None:
 
745
        if initiator is not False:
 
746
            if self.invalidated:
 
747
                self._warn_invalidated()
727
748
            self.attr.fire_remove_event(
728
749
                                    self.owner_state,
729
750
                                    self.owner_state.dict,
736
757
        fire_remove_event().
737
758
 
738
759
        """
 
760
        if self.invalidated:
 
761
            self._warn_invalidated()
739
762
        self.attr.fire_pre_remove_event(
740
763
                                    self.owner_state,
741
764
                                    self.owner_state.dict,
762
785
 
763
786
    :param values: An iterable of collection member instances
764
787
 
765
 
    :param existing_adapter: A :class:`.CollectionAdapter` of instances to be replaced
 
788
    :param existing_adapter: A :class:`.CollectionAdapter` of
 
789
     instances to be replaced
766
790
 
767
 
    :param new_adapter: An empty :class:`.CollectionAdapter` to load with ``values``
 
791
    :param new_adapter: An empty :class:`.CollectionAdapter`
 
792
     to load with ``values``
768
793
 
769
794
 
770
795
    """
772
797
        values = list(values)
773
798
 
774
799
    idset = util.IdentitySet
775
 
    constants = idset(existing_adapter or ()).intersection(values or ())
 
800
    existing_idset = idset(existing_adapter or ())
 
801
    constants = existing_idset.intersection(values or ())
776
802
    additions = idset(values or ()).difference(constants)
777
 
    removals = idset(existing_adapter or ()).difference(constants)
 
803
    removals = existing_idset.difference(constants)
778
804
 
779
805
    for member in values or ():
780
806
        if member in additions:
786
812
        for member in removals:
787
813
            existing_adapter.remove_with_event(member)
788
814
 
 
815
 
789
816
def prepare_instrumentation(factory):
790
817
    """Prepare a callable for future use as a collection class factory.
791
818
 
807
834
    # Did factory callable return a builtin?
808
835
    if cls in __canned_instrumentation:
809
836
        # Wrap it so that it returns our 'Instrumented*'
810
 
        factory = __converting_factory(factory)
 
837
        factory = __converting_factory(cls, factory)
811
838
        cls = factory()
812
839
 
813
840
    # Instrument the class if needed.
820
847
 
821
848
    return factory
822
849
 
823
 
def __converting_factory(original_factory):
824
 
    """Convert the type returned by collection factories on the fly.
825
850
 
826
 
    Given a collection factory that returns a builtin type (e.g. a list),
827
 
    return a wrapped function that converts that type to one of our
828
 
    instrumented types.
 
851
def __converting_factory(specimen_cls, original_factory):
 
852
    """Return a wrapper that converts a "canned" collection like
 
853
    set, dict, list into the Instrumented* version.
829
854
 
830
855
    """
 
856
 
 
857
    instrumented_cls = __canned_instrumentation[specimen_cls]
 
858
 
831
859
    def wrapper():
832
860
        collection = original_factory()
833
 
        type_ = type(collection)
834
 
        if type_ in __canned_instrumentation:
835
 
            # return an instrumented type initialized from the factory's
836
 
            # collection
837
 
            return __canned_instrumentation[type_](collection)
838
 
        else:
839
 
            raise sa_exc.InvalidRequestError(
840
 
                "Collection class factories must produce instances of a "
841
 
                "single class.")
842
 
    try:
843
 
        # often flawed but better than nothing
844
 
        wrapper.__name__ = "%sWrapper" % original_factory.__name__
845
 
        wrapper.__doc__ = original_factory.__doc__
846
 
    except:
847
 
        pass
 
861
        return instrumented_cls(collection)
 
862
 
 
863
    # often flawed but better than nothing
 
864
    wrapper.__name__ = "%sWrapper" % original_factory.__name__
 
865
    wrapper.__doc__ = original_factory.__doc__
 
866
 
848
867
    return wrapper
849
868
 
850
869
def _instrument_class(cls):
851
870
    """Modify methods in a class and install instrumentation."""
852
871
 
853
 
    # TODO: more formally document this as a decoratorless/Python 2.3
854
 
    # option for specifying instrumentation.  (likely doc'd here in code only,
855
 
    # not in online docs.)  Useful for C types too.
856
 
    #
857
 
    # __instrumentation__ = {
858
 
    #   'rolename': 'methodname', # ...
859
 
    #   'methods': {
860
 
    #     'methodname': ('fire_{append,remove}_event', argspec,
861
 
    #                    'fire_{append,remove}_event'),
862
 
    #     'append': ('fire_append_event', 1, None),
863
 
    #     '__setitem__': ('fire_append_event', 1, 'fire_remove_event'),
864
 
    #     'pop': (None, None, 'fire_remove_event'),
865
 
    #     }
866
 
    #  }
867
 
 
868
872
    # In the normal call flow, a request for any of the 3 basic collection
869
873
    # types is transformed into one of our trivial subclasses
870
874
    # (e.g. InstrumentedList).  Catch anything else that sneaks in here...
873
877
            "Can not instrument a built-in type. Use a "
874
878
            "subclass, even a trivial one.")
875
879
 
 
880
    roles = {}
 
881
    methods = {}
 
882
 
 
883
    # search for _sa_instrument_role-decorated methods in
 
884
    # method resolution order, assign to roles
 
885
    for supercls in cls.__mro__:
 
886
        for name, method in vars(supercls).items():
 
887
            if not util.callable(method):
 
888
                continue
 
889
 
 
890
            # note role declarations
 
891
            if hasattr(method, '_sa_instrument_role'):
 
892
                role = method._sa_instrument_role
 
893
                assert role in ('appender', 'remover', 'iterator',
 
894
                                'linker', 'converter')
 
895
                roles.setdefault(role, name)
 
896
 
 
897
            # transfer instrumentation requests from decorated function
 
898
            # to the combined queue
 
899
            before, after = None, None
 
900
            if hasattr(method, '_sa_instrument_before'):
 
901
                op, argument = method._sa_instrument_before
 
902
                assert op in ('fire_append_event', 'fire_remove_event')
 
903
                before = op, argument
 
904
            if hasattr(method, '_sa_instrument_after'):
 
905
                op = method._sa_instrument_after
 
906
                assert op in ('fire_append_event', 'fire_remove_event')
 
907
                after = op
 
908
            if before:
 
909
                methods[name] = before[0], before[1], after
 
910
            elif after:
 
911
                methods[name] = None, None, after
 
912
 
 
913
    # see if this class has "canned" roles based on a known
 
914
    # collection type (dict, set, list).  Apply those roles
 
915
    # as needed to the "roles" dictionary, and also
 
916
    # prepare "decorator" methods
876
917
    collection_type = util.duck_type_collection(cls)
877
918
    if collection_type in __interfaces:
878
 
        roles = __interfaces[collection_type].copy()
879
 
        decorators = roles.pop('_decorators', {})
880
 
    else:
881
 
        roles, decorators = {}, {}
882
 
 
883
 
    if hasattr(cls, '__instrumentation__'):
884
 
        roles.update(copy.deepcopy(getattr(cls, '__instrumentation__')))
885
 
 
886
 
    methods = roles.pop('methods', {})
887
 
 
888
 
    for name in dir(cls):
889
 
        method = getattr(cls, name, None)
890
 
        if not util.callable(method):
891
 
            continue
892
 
 
893
 
        # note role declarations
894
 
        if hasattr(method, '_sa_instrument_role'):
895
 
            role = method._sa_instrument_role
896
 
            assert role in ('appender', 'remover', 'iterator',
897
 
                            'link', 'converter')
898
 
            roles[role] = name
899
 
 
900
 
        # transfer instrumentation requests from decorated function
901
 
        # to the combined queue
902
 
        before, after = None, None
903
 
        if hasattr(method, '_sa_instrument_before'):
904
 
            op, argument = method._sa_instrument_before
905
 
            assert op in ('fire_append_event', 'fire_remove_event')
906
 
            before = op, argument
907
 
        if hasattr(method, '_sa_instrument_after'):
908
 
            op = method._sa_instrument_after
909
 
            assert op in ('fire_append_event', 'fire_remove_event')
910
 
            after = op
911
 
        if before:
912
 
            methods[name] = before[0], before[1], after
913
 
        elif after:
914
 
            methods[name] = None, None, after
915
 
 
916
 
    # apply ABC auto-decoration to methods that need it
917
 
 
918
 
    for method, decorator in decorators.items():
919
 
        fn = getattr(cls, method, None)
920
 
        if (fn and method not in methods and
921
 
            not hasattr(fn, '_sa_instrumented')):
922
 
            setattr(cls, method, decorator(fn))
 
919
        canned_roles, decorators = __interfaces[collection_type]
 
920
        for role, name in canned_roles.items():
 
921
            roles.setdefault(role, name)
 
922
 
 
923
        # apply ABC auto-decoration to methods that need it
 
924
        for method, decorator in decorators.items():
 
925
            fn = getattr(cls, method, None)
 
926
            if (fn and method not in methods and
 
927
                not hasattr(fn, '_sa_instrumented')):
 
928
                setattr(cls, method, decorator(fn))
923
929
 
924
930
    # ensure all roles are present, and apply implicit instrumentation if
925
931
    # needed
946
952
 
947
953
    # apply ad-hoc instrumentation from decorators, class-level defaults
948
954
    # and implicit role declarations
949
 
    for method, (before, argument, after) in methods.items():
950
 
        setattr(cls, method,
951
 
                _instrument_membership_mutator(getattr(cls, method),
 
955
    for method_name, (before, argument, after) in methods.items():
 
956
        setattr(cls, method_name,
 
957
                _instrument_membership_mutator(getattr(cls, method_name),
952
958
                                               before, argument, after))
953
959
    # intern the role map
954
 
    for role, method in roles.items():
955
 
        setattr(cls, '_sa_%s' % role, getattr(cls, method))
 
960
    for role, method_name in roles.items():
 
961
        setattr(cls, '_sa_%s' % role, getattr(cls, method_name))
956
962
 
957
963
    setattr(cls, '_sa_instrumented', id(cls))
958
964
 
 
965
 
959
966
def _instrument_membership_mutator(method, before, argument, after):
960
967
    """Route method args and/or return value through the collection adapter."""
961
968
    # This isn't smart enough to handle @adds(1) for 'def fn(self, (a, b))'
1004
1011
            if res is not None:
1005
1012
                getattr(executor, after)(res, initiator)
1006
1013
            return res
1007
 
    try:
1008
 
        wrapper._sa_instrumented = True
1009
 
        wrapper.__name__ = method.__name__
1010
 
        wrapper.__doc__ = method.__doc__
1011
 
    except:
1012
 
        pass
 
1014
 
 
1015
    wrapper._sa_instrumented = True
 
1016
    if hasattr(method, "_sa_instrument_role"):
 
1017
        wrapper._sa_instrument_role = method._sa_instrument_role
 
1018
    wrapper.__name__ = method.__name__
 
1019
    wrapper.__doc__ = method.__doc__
1013
1020
    return wrapper
1014
1021
 
 
1022
 
1015
1023
def __set(collection, item, _sa_initiator=None):
1016
1024
    """Run set events, may eventually be inlined into decorators."""
1017
1025
 
1018
 
    if _sa_initiator is not False and item is not None:
 
1026
    if _sa_initiator is not False:
1019
1027
        executor = getattr(collection, '_sa_adapter', None)
1020
1028
        if executor:
1021
1029
            item = getattr(executor, 'fire_append_event')(item, _sa_initiator)
1022
1030
    return item
1023
1031
 
 
1032
 
1024
1033
def __del(collection, item, _sa_initiator=None):
1025
1034
    """Run del events, may eventually be inlined into decorators."""
1026
 
    if _sa_initiator is not False and item is not None:
 
1035
    if _sa_initiator is not False:
1027
1036
        executor = getattr(collection, '_sa_adapter', None)
1028
1037
        if executor:
1029
1038
            getattr(executor, 'fire_remove_event')(item, _sa_initiator)
1030
1039
 
 
1040
 
1031
1041
def __before_delete(collection, _sa_initiator=None):
1032
1042
    """Special method to run 'commit existing value' methods"""
1033
1043
    executor = getattr(collection, '_sa_adapter', None)
1034
1044
    if executor:
1035
1045
        getattr(executor, 'fire_pre_remove_event')(_sa_initiator)
1036
1046
 
 
1047
 
1037
1048
def _list_decorators():
1038
1049
    """Tailored instrumentation wrappers for any list-like class."""
1039
1050
 
1171
1182
    l.pop('_tidy')
1172
1183
    return l
1173
1184
 
 
1185
 
1174
1186
def _dict_decorators():
1175
1187
    """Tailored instrumentation wrappers for any dict-like mapping class."""
1176
1188
 
1235
1247
        _tidy(setdefault)
1236
1248
        return setdefault
1237
1249
 
1238
 
    if sys.version_info < (2, 4):
1239
 
        def update(fn):
1240
 
            def update(self, other):
1241
 
                for key in other.keys():
1242
 
                    if key not in self or self[key] is not other[key]:
1243
 
                        self[key] = other[key]
1244
 
            _tidy(update)
1245
 
            return update
1246
 
    else:
1247
 
        def update(fn):
1248
 
            def update(self, __other=Unspecified, **kw):
1249
 
                if __other is not Unspecified:
1250
 
                    if hasattr(__other, 'keys'):
1251
 
                        for key in __other.keys():
1252
 
                            if (key not in self or
1253
 
                                self[key] is not __other[key]):
1254
 
                                self[key] = __other[key]
1255
 
                    else:
1256
 
                        for key, value in __other:
1257
 
                            if key not in self or self[key] is not value:
1258
 
                                self[key] = value
1259
 
                for key in kw:
1260
 
                    if key not in self or self[key] is not kw[key]:
1261
 
                        self[key] = kw[key]
1262
 
            _tidy(update)
1263
 
            return update
 
1250
    def update(fn):
 
1251
        def update(self, __other=Unspecified, **kw):
 
1252
            if __other is not Unspecified:
 
1253
                if hasattr(__other, 'keys'):
 
1254
                    for key in __other.keys():
 
1255
                        if (key not in self or
 
1256
                            self[key] is not __other[key]):
 
1257
                            self[key] = __other[key]
 
1258
                else:
 
1259
                    for key, value in __other:
 
1260
                        if key not in self or self[key] is not value:
 
1261
                            self[key] = value
 
1262
            for key in kw:
 
1263
                if key not in self or self[key] is not kw[key]:
 
1264
                    self[key] = kw[key]
 
1265
        _tidy(update)
 
1266
        return update
1264
1267
 
1265
1268
    l = locals().copy()
1266
1269
    l.pop('_tidy')
1273
1276
    import sets
1274
1277
    _set_binop_bases = (set, frozenset, sets.BaseSet)
1275
1278
 
 
1279
 
1276
1280
def _set_binops_check_strict(self, obj):
1277
 
    """Allow only set, frozenset and self.__class__-derived objects in binops."""
 
1281
    """Allow only set, frozenset and self.__class__-derived
 
1282
    objects in binops."""
1278
1283
    return isinstance(obj, _set_binop_bases + (self.__class__,))
1279
1284
 
 
1285
 
1280
1286
def _set_binops_check_loose(self, obj):
1281
1287
    """Allow anything set-like to participate in set binops."""
1282
1288
    return (isinstance(obj, _set_binop_bases + (self.__class__,)) or
1301
1307
        _tidy(add)
1302
1308
        return add
1303
1309
 
1304
 
    if sys.version_info < (2, 4):
1305
 
        def discard(fn):
1306
 
            def discard(self, value, _sa_initiator=None):
1307
 
                if value in self:
1308
 
                    self.remove(value, _sa_initiator)
1309
 
            _tidy(discard)
1310
 
            return discard
1311
 
    else:
1312
 
        def discard(fn):
1313
 
            def discard(self, value, _sa_initiator=None):
 
1310
    def discard(fn):
 
1311
        def discard(self, value, _sa_initiator=None):
 
1312
            # testlib.pragma exempt:__hash__
 
1313
            if value in self:
 
1314
                __del(self, value, _sa_initiator)
1314
1315
                # testlib.pragma exempt:__hash__
1315
 
                if value in self:
1316
 
                    __del(self, value, _sa_initiator)
1317
 
                    # testlib.pragma exempt:__hash__
1318
 
                fn(self, value)
1319
 
            _tidy(discard)
1320
 
            return discard
 
1316
            fn(self, value)
 
1317
        _tidy(discard)
 
1318
        return discard
1321
1319
 
1322
1320
    def remove(fn):
1323
1321
        def remove(self, value, _sa_initiator=None):
1442
1440
class InstrumentedList(list):
1443
1441
    """An instrumented version of the built-in list."""
1444
1442
 
1445
 
    __instrumentation__ = {
1446
 
       'appender': 'append',
1447
 
       'remover': 'remove',
1448
 
       'iterator': '__iter__', }
1449
1443
 
1450
1444
class InstrumentedSet(set):
1451
1445
    """An instrumented version of the built-in set."""
1452
1446
 
1453
 
    __instrumentation__ = {
1454
 
       'appender': 'add',
1455
 
       'remover': 'remove',
1456
 
       'iterator': '__iter__', }
1457
1447
 
1458
1448
class InstrumentedDict(dict):
1459
1449
    """An instrumented version of the built-in dict."""
1460
1450
 
1461
 
    # Py3K
1462
 
    #__instrumentation__ = {
1463
 
    #    'iterator': 'values', }
1464
 
    # Py2K
1465
 
    __instrumentation__ = {
1466
 
        'iterator': 'itervalues', }
1467
 
    # end Py2K
1468
1451
 
1469
1452
__canned_instrumentation = {
1470
1453
    list: InstrumentedList,
1473
1456
    }
1474
1457
 
1475
1458
__interfaces = {
1476
 
    list: {'appender': 'append',
1477
 
           'remover': 'remove',
1478
 
           'iterator': '__iter__',
1479
 
           '_decorators': _list_decorators(), },
1480
 
    set: {'appender': 'add',
 
1459
    list: (
 
1460
        {'appender': 'append', 'remover': 'remove',
 
1461
           'iterator': '__iter__'}, _list_decorators()
 
1462
        ),
 
1463
 
 
1464
    set: ({'appender': 'add',
1481
1465
          'remover': 'remove',
1482
 
          'iterator': '__iter__',
1483
 
          '_decorators': _set_decorators(), },
 
1466
          'iterator': '__iter__'}, _set_decorators()
 
1467
        ),
 
1468
 
1484
1469
    # decorators are required for dicts and object collections.
1485
1470
    # Py3K
1486
 
    #dict: {'iterator': 'values',
1487
 
    #       '_decorators': _dict_decorators(), },
 
1471
    #dict: ({'iterator': 'values'}, _dict_decorators()),
1488
1472
    # Py2K
1489
 
    dict: {'iterator': 'itervalues',
1490
 
           '_decorators': _dict_decorators(), },
 
1473
    dict: ({'iterator': 'itervalues'}, _dict_decorators()),
1491
1474
    # end Py2K
1492
 
    # < 0.4 compatible naming, deprecated- use decorators instead.
1493
 
    None: {}
1494
1475
    }
1495
1476
 
 
1477
 
1496
1478
class MappedCollection(dict):
1497
1479
    """A basic dictionary-based collection class.
1498
1480
 
1499
 
    Extends dict with the minimal bag semantics that collection classes require.
1500
 
    ``set`` and ``remove`` are implemented in terms of a keying function: any
1501
 
    callable that takes an object and returns an object for use as a dictionary
1502
 
    key.
 
1481
    Extends dict with the minimal bag semantics that collection
 
1482
    classes require. ``set`` and ``remove`` are implemented in terms
 
1483
    of a keying function: any callable that takes an object and
 
1484
    returns an object for use as a dictionary key.
1503
1485
 
1504
1486
    """
1505
1487
 
1519
1501
        """
1520
1502
        self.keyfunc = keyfunc
1521
1503
 
 
1504
    @collection.appender
 
1505
    @collection.internally_instrumented
1522
1506
    def set(self, value, _sa_initiator=None):
1523
1507
        """Add an item by value, consulting the keyfunc for the key."""
1524
1508
 
1525
1509
        key = self.keyfunc(value)
1526
1510
        self.__setitem__(key, value, _sa_initiator)
1527
 
    set = collection.internally_instrumented(set)
1528
 
    set = collection.appender(set)
1529
1511
 
 
1512
    @collection.remover
 
1513
    @collection.internally_instrumented
1530
1514
    def remove(self, value, _sa_initiator=None):
1531
1515
        """Remove an item by value, consulting the keyfunc for the key."""
1532
1516
 
1541
1525
                "values after flush?" %
1542
1526
                (value, self[key], key))
1543
1527
        self.__delitem__(key, _sa_initiator)
1544
 
    remove = collection.internally_instrumented(remove)
1545
 
    remove = collection.remover(remove)
1546
1528
 
 
1529
    @collection.converter
1547
1530
    def _convert(self, dictlike):
1548
1531
        """Validate and convert a dict-like object into values for set()ing.
1549
1532
 
1561
1544
            new_key = self.keyfunc(value)
1562
1545
            if incoming_key != new_key:
1563
1546
                raise TypeError(
1564
 
                    "Found incompatible key %r for value %r; this collection's "
 
1547
                    "Found incompatible key %r for value %r; this "
 
1548
                    "collection's "
1565
1549
                    "keying function requires a key of %r for this value." % (
1566
1550
                    incoming_key, value, new_key))
1567
1551
            yield value
1568
 
    _convert = collection.converter(_convert)
1569
1552
 
1570
1553
# ensure instrumentation is associated with
1571
1554
# these built-in classes; if a user-defined class
1575
1558
_instrument_class(MappedCollection)
1576
1559
_instrument_class(InstrumentedList)
1577
1560
_instrument_class(InstrumentedSet)
1578