97
97
``collection_adapter(self)`` will retrieve an object that you can use for
98
98
explicit control over triggering append and remove events.
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.
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")
114
115
__all__ = ['collection', 'collection_adapter',
115
116
'mapped_collection', 'column_mapped_collection',
394
400
def internally_instrumented(fn):
395
401
"""Tag the method as instrumented.
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::
402
409
# normally an 'extend' method on a list-like class would be
403
410
# automatically intercepted and re-implemented in terms of
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)
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)
607
623
def adapt_like_to_iterable(self, obj):
608
624
"""Converts collection-compatible objects to an iterable of values.
823
def __converting_factory(original_factory):
824
"""Convert the type returned by collection factories on the fly.
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
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.
857
instrumented_cls = __canned_instrumentation[specimen_cls]
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
837
return __canned_instrumentation[type_](collection)
839
raise sa_exc.InvalidRequestError(
840
"Collection class factories must produce instances of a "
843
# often flawed but better than nothing
844
wrapper.__name__ = "%sWrapper" % original_factory.__name__
845
wrapper.__doc__ = original_factory.__doc__
861
return instrumented_cls(collection)
863
# often flawed but better than nothing
864
wrapper.__name__ = "%sWrapper" % original_factory.__name__
865
wrapper.__doc__ = original_factory.__doc__
850
869
def _instrument_class(cls):
851
870
"""Modify methods in a class and install instrumentation."""
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.
857
# __instrumentation__ = {
858
# 'rolename': 'methodname', # ...
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'),
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.")
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):
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)
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')
909
methods[name] = before[0], before[1], after
911
methods[name] = None, None, after
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', {})
881
roles, decorators = {}, {}
883
if hasattr(cls, '__instrumentation__'):
884
roles.update(copy.deepcopy(getattr(cls, '__instrumentation__')))
886
methods = roles.pop('methods', {})
888
for name in dir(cls):
889
method = getattr(cls, name, None)
890
if not util.callable(method):
893
# note role declarations
894
if hasattr(method, '_sa_instrument_role'):
895
role = method._sa_instrument_role
896
assert role in ('appender', 'remover', 'iterator',
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')
912
methods[name] = before[0], before[1], after
914
methods[name] = None, None, after
916
# apply ABC auto-decoration to methods that need it
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)
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))
924
930
# ensure all roles are present, and apply implicit instrumentation if
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():
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))
957
963
setattr(cls, '_sa_instrumented', id(cls))
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)
1008
wrapper._sa_instrumented = True
1009
wrapper.__name__ = method.__name__
1010
wrapper.__doc__ = method.__doc__
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__
1015
1023
def __set(collection, item, _sa_initiator=None):
1016
1024
"""Run set events, may eventually be inlined into decorators."""
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)
1021
1029
item = getattr(executor, 'fire_append_event')(item, _sa_initiator)
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)
1029
1038
getattr(executor, 'fire_remove_event')(item, _sa_initiator)
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)
1035
1045
getattr(executor, 'fire_pre_remove_event')(_sa_initiator)
1037
1048
def _list_decorators():
1038
1049
"""Tailored instrumentation wrappers for any list-like class."""
1235
1247
_tidy(setdefault)
1236
1248
return setdefault
1238
if sys.version_info < (2, 4):
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]
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]
1256
for key, value in __other:
1257
if key not in self or self[key] is not value:
1260
if key not in self or self[key] is not kw[key]:
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]
1259
for key, value in __other:
1260
if key not in self or self[key] is not value:
1263
if key not in self or self[key] is not kw[key]:
1265
1268
l = locals().copy()
1304
if sys.version_info < (2, 4):
1306
def discard(self, value, _sa_initiator=None):
1308
self.remove(value, _sa_initiator)
1313
def discard(self, value, _sa_initiator=None):
1311
def discard(self, value, _sa_initiator=None):
1312
# testlib.pragma exempt:__hash__
1314
__del(self, value, _sa_initiator)
1314
1315
# testlib.pragma exempt:__hash__
1316
__del(self, value, _sa_initiator)
1317
# testlib.pragma exempt:__hash__
1322
1320
def remove(fn):
1323
1321
def remove(self, value, _sa_initiator=None):
1475
1458
__interfaces = {
1476
list: {'appender': 'append',
1477
'remover': 'remove',
1478
'iterator': '__iter__',
1479
'_decorators': _list_decorators(), },
1480
set: {'appender': 'add',
1460
{'appender': 'append', 'remover': 'remove',
1461
'iterator': '__iter__'}, _list_decorators()
1464
set: ({'appender': 'add',
1481
1465
'remover': 'remove',
1482
'iterator': '__iter__',
1483
'_decorators': _set_decorators(), },
1466
'iterator': '__iter__'}, _set_decorators()
1484
1469
# decorators are required for dicts and object collections.
1486
#dict: {'iterator': 'values',
1487
# '_decorators': _dict_decorators(), },
1471
#dict: ({'iterator': 'values'}, _dict_decorators()),
1489
dict: {'iterator': 'itervalues',
1490
'_decorators': _dict_decorators(), },
1473
dict: ({'iterator': 'itervalues'}, _dict_decorators()),
1492
# < 0.4 compatible naming, deprecated- use decorators instead.
1496
1478
class MappedCollection(dict):
1497
1479
"""A basic dictionary-based collection class.
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
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.