path_registry.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. # orm/path_registry.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """Path tracking utilities, representing mapper graph traversals.
  8. """
  9. from .. import inspection
  10. from .. import util
  11. from .. import exc
  12. from itertools import chain
  13. from .base import class_mapper
  14. import logging
  15. log = logging.getLogger(__name__)
  16. def _unreduce_path(path):
  17. return PathRegistry.deserialize(path)
  18. _WILDCARD_TOKEN = "*"
  19. _DEFAULT_TOKEN = "_sa_default"
  20. class PathRegistry(object):
  21. """Represent query load paths and registry functions.
  22. Basically represents structures like:
  23. (<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
  24. These structures are generated by things like
  25. query options (joinedload(), subqueryload(), etc.) and are
  26. used to compose keys stored in the query._attributes dictionary
  27. for various options.
  28. They are then re-composed at query compile/result row time as
  29. the query is formed and as rows are fetched, where they again
  30. serve to compose keys to look up options in the context.attributes
  31. dictionary, which is copied from query._attributes.
  32. The path structure has a limited amount of caching, where each
  33. "root" ultimately pulls from a fixed registry associated with
  34. the first mapper, that also contains elements for each of its
  35. property keys. However paths longer than two elements, which
  36. are the exception rather than the rule, are generated on an
  37. as-needed basis.
  38. """
  39. is_token = False
  40. is_root = False
  41. def __eq__(self, other):
  42. return other is not None and \
  43. self.path == other.path
  44. def set(self, attributes, key, value):
  45. log.debug("set '%s' on path '%s' to '%s'", key, self, value)
  46. attributes[(key, self.path)] = value
  47. def setdefault(self, attributes, key, value):
  48. log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value)
  49. attributes.setdefault((key, self.path), value)
  50. def get(self, attributes, key, value=None):
  51. key = (key, self.path)
  52. if key in attributes:
  53. return attributes[key]
  54. else:
  55. return value
  56. def __len__(self):
  57. return len(self.path)
  58. @property
  59. def length(self):
  60. return len(self.path)
  61. def pairs(self):
  62. path = self.path
  63. for i in range(0, len(path), 2):
  64. yield path[i], path[i + 1]
  65. def contains_mapper(self, mapper):
  66. for path_mapper in [
  67. self.path[i] for i in range(0, len(self.path), 2)
  68. ]:
  69. if path_mapper.is_mapper and \
  70. path_mapper.isa(mapper):
  71. return True
  72. else:
  73. return False
  74. def contains(self, attributes, key):
  75. return (key, self.path) in attributes
  76. def __reduce__(self):
  77. return _unreduce_path, (self.serialize(), )
  78. def serialize(self):
  79. path = self.path
  80. return list(zip(
  81. [m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
  82. [path[i].key for i in range(1, len(path), 2)] + [None]
  83. ))
  84. @classmethod
  85. def deserialize(cls, path):
  86. if path is None:
  87. return None
  88. p = tuple(chain(*[(class_mapper(mcls),
  89. class_mapper(mcls).attrs[key]
  90. if key is not None else None)
  91. for mcls, key in path]))
  92. if p and p[-1] is None:
  93. p = p[0:-1]
  94. return cls.coerce(p)
  95. @classmethod
  96. def per_mapper(cls, mapper):
  97. return EntityRegistry(
  98. cls.root, mapper
  99. )
  100. @classmethod
  101. def coerce(cls, raw):
  102. return util.reduce(lambda prev, next: prev[next], raw, cls.root)
  103. def token(self, token):
  104. if token.endswith(':' + _WILDCARD_TOKEN):
  105. return TokenRegistry(self, token)
  106. elif token.endswith(":" + _DEFAULT_TOKEN):
  107. return TokenRegistry(self.root, token)
  108. else:
  109. raise exc.ArgumentError("invalid token: %s" % token)
  110. def __add__(self, other):
  111. return util.reduce(
  112. lambda prev, next: prev[next],
  113. other.path, self)
  114. def __repr__(self):
  115. return "%s(%r)" % (self.__class__.__name__, self.path, )
  116. class RootRegistry(PathRegistry):
  117. """Root registry, defers to mappers so that
  118. paths are maintained per-root-mapper.
  119. """
  120. path = ()
  121. has_entity = False
  122. is_aliased_class = False
  123. is_root = True
  124. def __getitem__(self, entity):
  125. return entity._path_registry
  126. PathRegistry.root = RootRegistry()
  127. class TokenRegistry(PathRegistry):
  128. def __init__(self, parent, token):
  129. self.token = token
  130. self.parent = parent
  131. self.path = parent.path + (token,)
  132. has_entity = False
  133. is_token = True
  134. def generate_for_superclasses(self):
  135. if not self.parent.is_aliased_class and not self.parent.is_root:
  136. for ent in self.parent.mapper.iterate_to_root():
  137. yield TokenRegistry(self.parent.parent[ent], self.token)
  138. else:
  139. yield self
  140. def __getitem__(self, entity):
  141. raise NotImplementedError()
  142. class PropRegistry(PathRegistry):
  143. def __init__(self, parent, prop):
  144. # restate this path in terms of the
  145. # given MapperProperty's parent.
  146. insp = inspection.inspect(parent[-1])
  147. if not insp.is_aliased_class or insp._use_mapper_path:
  148. parent = parent.parent[prop.parent]
  149. elif insp.is_aliased_class and insp.with_polymorphic_mappers:
  150. if prop.parent is not insp.mapper and \
  151. prop.parent in insp.with_polymorphic_mappers:
  152. subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
  153. parent = parent.parent[subclass_entity]
  154. self.prop = prop
  155. self.parent = parent
  156. self.path = parent.path + (prop,)
  157. self._wildcard_path_loader_key = (
  158. "loader",
  159. self.parent.path + self.prop._wildcard_token
  160. )
  161. self._default_path_loader_key = self.prop._default_path_loader_key
  162. self._loader_key = ("loader", self.path)
  163. def __str__(self):
  164. return " -> ".join(
  165. str(elem) for elem in self.path
  166. )
  167. @util.memoized_property
  168. def has_entity(self):
  169. return hasattr(self.prop, "mapper")
  170. @util.memoized_property
  171. def entity(self):
  172. return self.prop.mapper
  173. @property
  174. def mapper(self):
  175. return self.entity
  176. @property
  177. def entity_path(self):
  178. return self[self.entity]
  179. def __getitem__(self, entity):
  180. if isinstance(entity, (int, slice)):
  181. return self.path[entity]
  182. else:
  183. return EntityRegistry(
  184. self, entity
  185. )
  186. class EntityRegistry(PathRegistry, dict):
  187. is_aliased_class = False
  188. has_entity = True
  189. def __init__(self, parent, entity):
  190. self.key = entity
  191. self.parent = parent
  192. self.is_aliased_class = entity.is_aliased_class
  193. self.entity = entity
  194. self.path = parent.path + (entity,)
  195. self.entity_path = self
  196. @property
  197. def mapper(self):
  198. return inspection.inspect(self.entity).mapper
  199. def __bool__(self):
  200. return True
  201. __nonzero__ = __bool__
  202. def __getitem__(self, entity):
  203. if isinstance(entity, (int, slice)):
  204. return self.path[entity]
  205. else:
  206. return dict.__getitem__(self, entity)
  207. def __missing__(self, key):
  208. self[key] = item = PropRegistry(self, key)
  209. return item