123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- # orm/path_registry.py
- # Copyright (C) 2005-2017 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
- """Path tracking utilities, representing mapper graph traversals.
- """
- from .. import inspection
- from .. import util
- from .. import exc
- from itertools import chain
- from .base import class_mapper
- import logging
- log = logging.getLogger(__name__)
- def _unreduce_path(path):
- return PathRegistry.deserialize(path)
- _WILDCARD_TOKEN = "*"
- _DEFAULT_TOKEN = "_sa_default"
- class PathRegistry(object):
- """Represent query load paths and registry functions.
- Basically represents structures like:
- (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
- These structures are generated by things like
- query options (joinedload(), subqueryload(), etc.) and are
- used to compose keys stored in the query._attributes dictionary
- for various options.
- They are then re-composed at query compile/result row time as
- the query is formed and as rows are fetched, where they again
- serve to compose keys to look up options in the context.attributes
- dictionary, which is copied from query._attributes.
- The path structure has a limited amount of caching, where each
- "root" ultimately pulls from a fixed registry associated with
- the first mapper, that also contains elements for each of its
- property keys. However paths longer than two elements, which
- are the exception rather than the rule, are generated on an
- as-needed basis.
- """
- is_token = False
- is_root = False
- def __eq__(self, other):
- return other is not None and \
- self.path == other.path
- def set(self, attributes, key, value):
- log.debug("set '%s' on path '%s' to '%s'", key, self, value)
- attributes[(key, self.path)] = value
- def setdefault(self, attributes, key, value):
- log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value)
- attributes.setdefault((key, self.path), value)
- def get(self, attributes, key, value=None):
- key = (key, self.path)
- if key in attributes:
- return attributes[key]
- else:
- return value
- def __len__(self):
- return len(self.path)
- @property
- def length(self):
- return len(self.path)
- def pairs(self):
- path = self.path
- for i in range(0, len(path), 2):
- yield path[i], path[i + 1]
- def contains_mapper(self, mapper):
- for path_mapper in [
- self.path[i] for i in range(0, len(self.path), 2)
- ]:
- if path_mapper.is_mapper and \
- path_mapper.isa(mapper):
- return True
- else:
- return False
- def contains(self, attributes, key):
- return (key, self.path) in attributes
- def __reduce__(self):
- return _unreduce_path, (self.serialize(), )
- def serialize(self):
- path = self.path
- return list(zip(
- [m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
- [path[i].key for i in range(1, len(path), 2)] + [None]
- ))
- @classmethod
- def deserialize(cls, path):
- if path is None:
- return None
- p = tuple(chain(*[(class_mapper(mcls),
- class_mapper(mcls).attrs[key]
- if key is not None else None)
- for mcls, key in path]))
- if p and p[-1] is None:
- p = p[0:-1]
- return cls.coerce(p)
- @classmethod
- def per_mapper(cls, mapper):
- return EntityRegistry(
- cls.root, mapper
- )
- @classmethod
- def coerce(cls, raw):
- return util.reduce(lambda prev, next: prev[next], raw, cls.root)
- def token(self, token):
- if token.endswith(':' + _WILDCARD_TOKEN):
- return TokenRegistry(self, token)
- elif token.endswith(":" + _DEFAULT_TOKEN):
- return TokenRegistry(self.root, token)
- else:
- raise exc.ArgumentError("invalid token: %s" % token)
- def __add__(self, other):
- return util.reduce(
- lambda prev, next: prev[next],
- other.path, self)
- def __repr__(self):
- return "%s(%r)" % (self.__class__.__name__, self.path, )
- class RootRegistry(PathRegistry):
- """Root registry, defers to mappers so that
- paths are maintained per-root-mapper.
- """
- path = ()
- has_entity = False
- is_aliased_class = False
- is_root = True
- def __getitem__(self, entity):
- return entity._path_registry
- PathRegistry.root = RootRegistry()
- class TokenRegistry(PathRegistry):
- def __init__(self, parent, token):
- self.token = token
- self.parent = parent
- self.path = parent.path + (token,)
- has_entity = False
- is_token = True
- def generate_for_superclasses(self):
- if not self.parent.is_aliased_class and not self.parent.is_root:
- for ent in self.parent.mapper.iterate_to_root():
- yield TokenRegistry(self.parent.parent[ent], self.token)
- else:
- yield self
- def __getitem__(self, entity):
- raise NotImplementedError()
- class PropRegistry(PathRegistry):
- def __init__(self, parent, prop):
- # restate this path in terms of the
- # given MapperProperty's parent.
- insp = inspection.inspect(parent[-1])
- if not insp.is_aliased_class or insp._use_mapper_path:
- parent = parent.parent[prop.parent]
- elif insp.is_aliased_class and insp.with_polymorphic_mappers:
- if prop.parent is not insp.mapper and \
- prop.parent in insp.with_polymorphic_mappers:
- subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
- parent = parent.parent[subclass_entity]
- self.prop = prop
- self.parent = parent
- self.path = parent.path + (prop,)
- self._wildcard_path_loader_key = (
- "loader",
- self.parent.path + self.prop._wildcard_token
- )
- self._default_path_loader_key = self.prop._default_path_loader_key
- self._loader_key = ("loader", self.path)
- def __str__(self):
- return " -> ".join(
- str(elem) for elem in self.path
- )
- @util.memoized_property
- def has_entity(self):
- return hasattr(self.prop, "mapper")
- @util.memoized_property
- def entity(self):
- return self.prop.mapper
- @property
- def mapper(self):
- return self.entity
- @property
- def entity_path(self):
- return self[self.entity]
- def __getitem__(self, entity):
- if isinstance(entity, (int, slice)):
- return self.path[entity]
- else:
- return EntityRegistry(
- self, entity
- )
- class EntityRegistry(PathRegistry, dict):
- is_aliased_class = False
- has_entity = True
- def __init__(self, parent, entity):
- self.key = entity
- self.parent = parent
- self.is_aliased_class = entity.is_aliased_class
- self.entity = entity
- self.path = parent.path + (entity,)
- self.entity_path = self
- @property
- def mapper(self):
- return inspection.inspect(self.entity).mapper
- def __bool__(self):
- return True
- __nonzero__ = __bool__
- def __getitem__(self, entity):
- if isinstance(entity, (int, slice)):
- return self.path[entity]
- else:
- return dict.__getitem__(self, entity)
- def __missing__(self, key):
- self[key] = item = PropRegistry(self, key)
- return item
|