diff options
| author | mike bayer <mike_mp@zzzcomputing.com> | 2017-12-26 13:04:52 -0500 |
|---|---|---|
| committer | Gerrit Code Review <gerrit@ci.zzzcomputing.com> | 2017-12-26 13:04:52 -0500 |
| commit | fb964539b84152f30f46837713495562a833d64d (patch) | |
| tree | 9f156554215122a96e19efa273b90f8619922989 /lib | |
| parent | c7f698e9670d8353bdb6be7d12900528b99c95c2 (diff) | |
| parent | 50d9f1687a6e0c3ce9b62fe98b76b25af7b20c11 (diff) | |
| download | sqlalchemy-fb964539b84152f30f46837713495562a833d64d.tar.gz | |
Merge "Add an identity_token to the identity key"
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/ext/horizontal_shard.py | 34 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/loading.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 37 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/persistence.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 11 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/state.py | 34 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 50 |
7 files changed, 112 insertions, 61 deletions
diff --git a/lib/sqlalchemy/ext/horizontal_shard.py b/lib/sqlalchemy/ext/horizontal_shard.py index 8902ae606..c5cf98b40 100644 --- a/lib/sqlalchemy/ext/horizontal_shard.py +++ b/lib/sqlalchemy/ext/horizontal_shard.py @@ -15,6 +15,7 @@ the source distribution. """ +from .. import inspect from .. import util from ..orm.session import Session from ..orm.query import Query @@ -42,7 +43,7 @@ class ShardedQuery(Query): def _execute_and_instances(self, context): def iter_for_shard(shard_id): - context.attributes['shard_id'] = shard_id + context.attributes['shard_id'] = context.identity_token = shard_id result = self._connection_from_session( mapper=self._mapper_zero(), shard_id=shard_id).execute( @@ -62,6 +63,9 @@ class ShardedQuery(Query): return iter(partial) def _get_impl(self, ident, fallback_fn): + # TODO: the "ident" here should be getting the identity token + # which indicates that this area can likely be simplified, as the + # token will fall through into _execute_and_instances def _fallback(query, ident): if self._shard_id is not None: return fallback_fn(self, ident) @@ -75,7 +79,13 @@ class ShardedQuery(Query): else: return None - return super(ShardedQuery, self)._get_impl(ident, _fallback) + if self._shard_id is not None: + identity_token = self._shard_id + else: + identity_token = None + + return super(ShardedQuery, self)._get_impl( + ident, _fallback, identity_token=identity_token) class ShardedSession(Session): @@ -112,9 +122,24 @@ class ShardedSession(Session): for k in shards: self.bind_shard(k, shards[k]) + def _choose_shard_and_assign(self, mapper, instance, **kw): + if instance is not None: + state = inspect(instance) + if state.key: + token = state.key[2] + assert token is not None + return token + elif state.identity_token: + return state.identity_token + + shard_id = self.shard_chooser(mapper, instance, **kw) + if instance is not None: + state.identity_token = shard_id + return shard_id + def connection(self, mapper=None, instance=None, shard_id=None, **kwargs): if shard_id is None: - shard_id = self.shard_chooser(mapper, instance) + shard_id = self._choose_shard_and_assign(mapper, instance) if self.transaction is not None: return self.transaction.connection(mapper, shard_id=shard_id) @@ -128,7 +153,8 @@ class ShardedSession(Session): def get_bind(self, mapper, shard_id=None, instance=None, clause=None, **kw): if shard_id is None: - shard_id = self.shard_chooser(mapper, instance, clause=clause) + shard_id = self._choose_shard_and_assign( + mapper, instance, clause=clause) return self.__binds[shard_id] def bind_shard(self, shard_id, bind): diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index a25a1422d..a23cafac2 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -369,6 +369,7 @@ def _instance_processor( session_id = context.session.hash_key version_check = context.version_check runid = context.runid + identity_token = context.identity_token if not refresh_state and _polymorphic_from is not None: key = ('loader', path.path) @@ -430,7 +431,8 @@ def _instance_processor( # session, or we have to create a new one identitykey = ( identity_class, - tuple([row[column] for column in pk_cols]) + tuple([row[column] for column in pk_cols]), + identity_token ) instance = session_identity_map.get(identitykey) @@ -464,6 +466,7 @@ def _instance_processor( dict_ = instance_dict(instance) state = instance_state(instance) state.key = identitykey + state.identity_token = identity_token # attach instance to session. state.session_id = session_id diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 31a93f42e..8317c914b 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -2506,7 +2506,7 @@ class Mapper(InspectionAttr): else: return True - def identity_key_from_row(self, row, adapter=None): + def identity_key_from_row(self, row, identity_token=None, adapter=None): """Return an identity-map key for use in storing/retrieving an item from the identity map. @@ -2522,16 +2522,16 @@ class Mapper(InspectionAttr): pk_cols = [adapter.columns[c] for c in pk_cols] return self._identity_class, \ - tuple(row[column] for column in pk_cols) + tuple(row[column] for column in pk_cols), identity_token - def identity_key_from_primary_key(self, primary_key): + def identity_key_from_primary_key(self, primary_key, identity_token=None): """Return an identity-map key for use in storing/retrieving an item from an identity map. :param primary_key: A list of values indicating the identifier. """ - return self._identity_class, tuple(primary_key) + return self._identity_class, tuple(primary_key), identity_token def identity_key_from_instance(self, instance): """Return the identity key for the given instance, based on @@ -2546,17 +2546,18 @@ class Mapper(InspectionAttr): attribute name `key`. """ - return self.identity_key_from_primary_key( - self.primary_key_from_instance(instance)) + state = attributes.instance_state(instance) + return self._identity_key_from_state(state, attributes.PASSIVE_OFF) - def _identity_key_from_state(self, state): + def _identity_key_from_state( + self, state, passive=attributes.PASSIVE_RETURN_NEVER_SET): dict_ = state.dict manager = state.manager return self._identity_class, tuple([ - manager[self._columntoproperty[col].key]. - impl.get(state, dict_, attributes.PASSIVE_RETURN_NEVER_SET) - for col in self.primary_key - ]) + manager[prop.key]. + impl.get(state, dict_, passive) + for prop in self._identity_key_props + ]), state.identity_token def primary_key_from_instance(self, instance): """Return the list of primary key values for the given @@ -2569,17 +2570,9 @@ class Mapper(InspectionAttr): """ state = attributes.instance_state(instance) - return self._primary_key_from_state(state, attributes.PASSIVE_OFF) - - def _primary_key_from_state( - self, state, passive=attributes.PASSIVE_RETURN_NEVER_SET): - dict_ = state.dict - manager = state.manager - return [ - manager[prop.key]. - impl.get(state, dict_, passive) - for prop in self._identity_key_props - ] + identity_key = self._identity_key_from_state( + state, attributes.PASSIVE_OFF) + return identity_key[1] @_memoized_configured_property def _identity_key_props(self): diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index dfa05a85e..942ac2b24 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -1402,7 +1402,7 @@ class BulkEvaluate(BulkUD): # TODO: detect when the where clause is a trivial primary key match self.matched_objects = [ - obj for (cls, pk), obj in + obj for (cls, pk, identity_token), obj in query.session.identity_map.items() if issubclass(cls, target_cls) and eval_condition(obj)] diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 209bb6d6a..8668b312b 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -867,9 +867,10 @@ class Query(object): :return: The object instance, or ``None``. """ - return self._get_impl(ident, loading.load_on_ident) + return self._get_impl( + ident, loading.load_on_ident) - def _get_impl(self, ident, fallback_fn): + def _get_impl(self, ident, fallback_fn, identity_token=None): # convert composite types to individual args if hasattr(ident, '__composite_values__'): ident = ident.__composite_values__() @@ -884,7 +885,8 @@ class Query(object): "primary key for query.get(); primary key columns are %s" % ','.join("'%s'" % c for c in mapper.primary_key)) - key = mapper.identity_key_from_primary_key(ident) + key = mapper.identity_key_from_primary_key( + ident, identity_token=identity_token) if not self._populate_existing and \ not mapper.always_refresh and \ @@ -4127,7 +4129,7 @@ class QueryContext(object): 'eager_joins', 'create_eager_joins', 'propagate_options', 'attributes', 'statement', 'from_clause', 'whereclause', 'order_by', 'labels', '_for_update_arg', 'runid', 'partials', - 'post_load_paths' + 'post_load_paths', 'identity_token' ) def __init__(self, query): @@ -4164,6 +4166,7 @@ class QueryContext(object): self.propagate_options = set(o for o in query._with_options if o.propagate_to_loaders) self.attributes = query._attributes.copy() + self.identity_token = None class AliasOption(interfaces.MapperOption): diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 4964c22e6..04ccf7e3c 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -63,6 +63,7 @@ class InstanceState(interfaces.InspectionAttr): _load_pending = False _orphaned_outside_of_session = False is_instance = True + identity_token = None callables = () """A namespace where a per-state loader callable can be associated. @@ -462,21 +463,34 @@ class InstanceState(interfaces.InspectionAttr): if 'callables' in state_dict: self.callables = state_dict['callables'] - try: - self.expired_attributes = state_dict['expired_attributes'] - except KeyError: - self.expired_attributes = set() - # 0.9 and earlier compat - for k in list(self.callables): - if self.callables[k] is self: - self.expired_attributes.add(k) - del self.callables[k] + try: + self.expired_attributes = state_dict['expired_attributes'] + except KeyError: + self.expired_attributes = set() + # 0.9 and earlier compat + for k in list(self.callables): + if self.callables[k] is self: + self.expired_attributes.add(k) + del self.callables[k] + else: + if 'expired_attributes' in state_dict: + self.expired_attributes = state_dict['expired_attributes'] + else: + self.expired_attributes = set() self.__dict__.update([ (k, state_dict[k]) for k in ( - 'key', 'load_options', + 'key', 'load_options' ) if k in state_dict ]) + if self.key: + try: + self.identity_token = self.key[2] + except IndexError: + # 1.1 and earlier compat before identity_token + assert len(self.key) == 2 + self.key = self.key + (None, ) + self.identity_token = None if 'load_path' in state_dict: self.load_path = PathRegistry.\ diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 4267b79fb..ad2a74a34 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -214,7 +214,7 @@ def identity_key(*args, **kwargs): This function has several call styles: - * ``identity_key(class, ident)`` + * ``identity_key(class, ident, identity_token=token)`` This form receives a mapped class and a primary key scalar or tuple as an argument. @@ -222,10 +222,13 @@ def identity_key(*args, **kwargs): E.g.:: >>> identity_key(MyClass, (1, 2)) - (<class '__main__.MyClass'>, (1, 2)) + (<class '__main__.MyClass'>, (1, 2), None) :param class: mapped class (must be a positional argument) :param ident: primary key, may be a scalar or tuple argument. + ;param identity_token: optional identity token + + .. versionadded:: 1.2 added identity_token * ``identity_key(instance=instance)`` @@ -239,7 +242,7 @@ def identity_key(*args, **kwargs): >>> instance = MyClass(1, 2) >>> identity_key(instance=instance) - (<class '__main__.MyClass'>, (1, 2)) + (<class '__main__.MyClass'>, (1, 2), None) In this form, the given instance is ultimately run though :meth:`.Mapper.identity_key_from_instance`, which will have the @@ -248,7 +251,7 @@ def identity_key(*args, **kwargs): :param instance: object instance (must be given as a keyword arg) - * ``identity_key(class, row=row)`` + * ``identity_key(class, row=row, identity_token=token)`` This form is similar to the class/tuple form, except is passed a database result row as a :class:`.RowProxy` object. @@ -258,41 +261,50 @@ def identity_key(*args, **kwargs): >>> row = engine.execute("select * from table where a=1 and b=2").\ first() >>> identity_key(MyClass, row=row) - (<class '__main__.MyClass'>, (1, 2)) + (<class '__main__.MyClass'>, (1, 2), None) :param class: mapped class (must be a positional argument) :param row: :class:`.RowProxy` row returned by a :class:`.ResultProxy` (must be given as a keyword arg) + ;param identity_token: optional identity token + + .. versionadded:: 1.2 added identity_token """ if args: - if len(args) == 1: + row = None + largs = len(args) + if largs == 1: class_ = args[0] try: row = kwargs.pop("row") except KeyError: ident = kwargs.pop("ident") - elif len(args) == 2: - class_, ident = args - elif len(args) == 3: + elif largs in (2, 3): class_, ident = args else: raise sa_exc.ArgumentError( "expected up to three positional arguments, " - "got %s" % len(args)) + "got %s" % largs) + + identity_token = kwargs.pop("identity_token", None) if kwargs: raise sa_exc.ArgumentError("unknown keyword arguments: %s" % ", ".join(kwargs)) mapper = class_mapper(class_) - if "ident" in locals(): - return mapper.identity_key_from_primary_key(util.to_list(ident)) - return mapper.identity_key_from_row(row) - instance = kwargs.pop("instance") - if kwargs: - raise sa_exc.ArgumentError("unknown keyword arguments: %s" - % ", ".join(kwargs.keys)) - mapper = object_mapper(instance) - return mapper.identity_key_from_instance(instance) + if row is None: + return mapper.identity_key_from_primary_key( + util.to_list(ident), identity_token=identity_token) + else: + return mapper.identity_key_from_row( + row, identity_token=identity_token) + else: + instance = kwargs.pop("instance") + if kwargs: + raise sa_exc.ArgumentError("unknown keyword arguments: %s" + % ", ".join(kwargs.keys)) + mapper = object_mapper(instance) + return mapper.identity_key_from_instance(instance) class ORMAdapter(sql_util.ColumnAdapter): |
