interfaces.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. # orm/interfaces.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. """
  8. Contains various base classes used throughout the ORM.
  9. Defines some key base classes prominent within the internals,
  10. as well as the now-deprecated ORM extension classes.
  11. Other than the deprecated extensions, this module and the
  12. classes within are mostly private, though some attributes
  13. are exposed when inspecting mappings.
  14. """
  15. from __future__ import absolute_import
  16. from .. import util
  17. from ..sql import operators
  18. from .base import (ONETOMANY, MANYTOONE, MANYTOMANY,
  19. EXT_CONTINUE, EXT_STOP, NOT_EXTENSION)
  20. from .base import (InspectionAttr, InspectionAttr,
  21. InspectionAttrInfo, _MappedAttribute)
  22. import collections
  23. from .. import inspect
  24. from . import path_registry
  25. # imported later
  26. MapperExtension = SessionExtension = AttributeExtension = None
  27. __all__ = (
  28. 'AttributeExtension',
  29. 'EXT_CONTINUE',
  30. 'EXT_STOP',
  31. 'ONETOMANY',
  32. 'MANYTOMANY',
  33. 'MANYTOONE',
  34. 'NOT_EXTENSION',
  35. 'LoaderStrategy',
  36. 'MapperExtension',
  37. 'MapperOption',
  38. 'MapperProperty',
  39. 'PropComparator',
  40. 'SessionExtension',
  41. 'StrategizedProperty',
  42. )
  43. class MapperProperty(_MappedAttribute, InspectionAttr, util.MemoizedSlots):
  44. """Represent a particular class attribute mapped by :class:`.Mapper`.
  45. The most common occurrences of :class:`.MapperProperty` are the
  46. mapped :class:`.Column`, which is represented in a mapping as
  47. an instance of :class:`.ColumnProperty`,
  48. and a reference to another class produced by :func:`.relationship`,
  49. represented in the mapping as an instance of
  50. :class:`.RelationshipProperty`.
  51. """
  52. __slots__ = (
  53. '_configure_started', '_configure_finished', 'parent', 'key',
  54. 'info'
  55. )
  56. cascade = frozenset()
  57. """The set of 'cascade' attribute names.
  58. This collection is checked before the 'cascade_iterator' method is called.
  59. The collection typically only applies to a RelationshipProperty.
  60. """
  61. is_property = True
  62. """Part of the InspectionAttr interface; states this object is a
  63. mapper property.
  64. """
  65. def _memoized_attr_info(self):
  66. """Info dictionary associated with the object, allowing user-defined
  67. data to be associated with this :class:`.InspectionAttr`.
  68. The dictionary is generated when first accessed. Alternatively,
  69. it can be specified as a constructor argument to the
  70. :func:`.column_property`, :func:`.relationship`, or :func:`.composite`
  71. functions.
  72. .. versionadded:: 0.8 Added support for .info to all
  73. :class:`.MapperProperty` subclasses.
  74. .. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also
  75. available on extension types via the
  76. :attr:`.InspectionAttrInfo.info` attribute, so that it can apply
  77. to a wider variety of ORM and extension constructs.
  78. .. seealso::
  79. :attr:`.QueryableAttribute.info`
  80. :attr:`.SchemaItem.info`
  81. """
  82. return {}
  83. def setup(self, context, entity, path, adapter, **kwargs):
  84. """Called by Query for the purposes of constructing a SQL statement.
  85. Each MapperProperty associated with the target mapper processes the
  86. statement referenced by the query context, adding columns and/or
  87. criterion as appropriate.
  88. """
  89. def create_row_processor(self, context, path,
  90. mapper, result, adapter, populators):
  91. """Produce row processing functions and append to the given
  92. set of populators lists.
  93. """
  94. def cascade_iterator(self, type_, state, visited_instances=None,
  95. halt_on=None):
  96. """Iterate through instances related to the given instance for
  97. a particular 'cascade', starting with this MapperProperty.
  98. Return an iterator3-tuples (instance, mapper, state).
  99. Note that the 'cascade' collection on this MapperProperty is
  100. checked first for the given type before cascade_iterator is called.
  101. This method typically only applies to RelationshipProperty.
  102. """
  103. return iter(())
  104. def set_parent(self, parent, init):
  105. """Set the parent mapper that references this MapperProperty.
  106. This method is overridden by some subclasses to perform extra
  107. setup when the mapper is first known.
  108. """
  109. self.parent = parent
  110. def instrument_class(self, mapper):
  111. """Hook called by the Mapper to the property to initiate
  112. instrumentation of the class attribute managed by this
  113. MapperProperty.
  114. The MapperProperty here will typically call out to the
  115. attributes module to set up an InstrumentedAttribute.
  116. This step is the first of two steps to set up an InstrumentedAttribute,
  117. and is called early in the mapper setup process.
  118. The second step is typically the init_class_attribute step,
  119. called from StrategizedProperty via the post_instrument_class()
  120. hook. This step assigns additional state to the InstrumentedAttribute
  121. (specifically the "impl") which has been determined after the
  122. MapperProperty has determined what kind of persistence
  123. management it needs to do (e.g. scalar, object, collection, etc).
  124. """
  125. def __init__(self):
  126. self._configure_started = False
  127. self._configure_finished = False
  128. def init(self):
  129. """Called after all mappers are created to assemble
  130. relationships between mappers and perform other post-mapper-creation
  131. initialization steps.
  132. """
  133. self._configure_started = True
  134. self.do_init()
  135. self._configure_finished = True
  136. @property
  137. def class_attribute(self):
  138. """Return the class-bound descriptor corresponding to this
  139. :class:`.MapperProperty`.
  140. This is basically a ``getattr()`` call::
  141. return getattr(self.parent.class_, self.key)
  142. I.e. if this :class:`.MapperProperty` were named ``addresses``,
  143. and the class to which it is mapped is ``User``, this sequence
  144. is possible::
  145. >>> from sqlalchemy import inspect
  146. >>> mapper = inspect(User)
  147. >>> addresses_property = mapper.attrs.addresses
  148. >>> addresses_property.class_attribute is User.addresses
  149. True
  150. >>> User.addresses.property is addresses_property
  151. True
  152. """
  153. return getattr(self.parent.class_, self.key)
  154. def do_init(self):
  155. """Perform subclass-specific initialization post-mapper-creation
  156. steps.
  157. This is a template method called by the ``MapperProperty``
  158. object's init() method.
  159. """
  160. def post_instrument_class(self, mapper):
  161. """Perform instrumentation adjustments that need to occur
  162. after init() has completed.
  163. The given Mapper is the Mapper invoking the operation, which
  164. may not be the same Mapper as self.parent in an inheritance
  165. scenario; however, Mapper will always at least be a sub-mapper of
  166. self.parent.
  167. This method is typically used by StrategizedProperty, which delegates
  168. it to LoaderStrategy.init_class_attribute() to perform final setup
  169. on the class-bound InstrumentedAttribute.
  170. """
  171. def merge(self, session, source_state, source_dict, dest_state,
  172. dest_dict, load, _recursive, _resolve_conflict_map):
  173. """Merge the attribute represented by this ``MapperProperty``
  174. from source to destination object.
  175. """
  176. def __repr__(self):
  177. return '<%s at 0x%x; %s>' % (
  178. self.__class__.__name__,
  179. id(self), getattr(self, 'key', 'no key'))
  180. class PropComparator(operators.ColumnOperators):
  181. r"""Defines SQL operators for :class:`.MapperProperty` objects.
  182. SQLAlchemy allows for operators to
  183. be redefined at both the Core and ORM level. :class:`.PropComparator`
  184. is the base class of operator redefinition for ORM-level operations,
  185. including those of :class:`.ColumnProperty`,
  186. :class:`.RelationshipProperty`, and :class:`.CompositeProperty`.
  187. .. note:: With the advent of Hybrid properties introduced in SQLAlchemy
  188. 0.7, as well as Core-level operator redefinition in
  189. SQLAlchemy 0.8, the use case for user-defined :class:`.PropComparator`
  190. instances is extremely rare. See :ref:`hybrids_toplevel` as well
  191. as :ref:`types_operators`.
  192. User-defined subclasses of :class:`.PropComparator` may be created. The
  193. built-in Python comparison and math operator methods, such as
  194. :meth:`.operators.ColumnOperators.__eq__`,
  195. :meth:`.operators.ColumnOperators.__lt__`, and
  196. :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
  197. new operator behavior. The custom :class:`.PropComparator` is passed to
  198. the :class:`.MapperProperty` instance via the ``comparator_factory``
  199. argument. In each case,
  200. the appropriate subclass of :class:`.PropComparator` should be used::
  201. # definition of custom PropComparator subclasses
  202. from sqlalchemy.orm.properties import \
  203. ColumnProperty,\
  204. CompositeProperty,\
  205. RelationshipProperty
  206. class MyColumnComparator(ColumnProperty.Comparator):
  207. def __eq__(self, other):
  208. return self.__clause_element__() == other
  209. class MyRelationshipComparator(RelationshipProperty.Comparator):
  210. def any(self, expression):
  211. "define the 'any' operation"
  212. # ...
  213. class MyCompositeComparator(CompositeProperty.Comparator):
  214. def __gt__(self, other):
  215. "redefine the 'greater than' operation"
  216. return sql.and_(*[a>b for a, b in
  217. zip(self.__clause_element__().clauses,
  218. other.__composite_values__())])
  219. # application of custom PropComparator subclasses
  220. from sqlalchemy.orm import column_property, relationship, composite
  221. from sqlalchemy import Column, String
  222. class SomeMappedClass(Base):
  223. some_column = column_property(Column("some_column", String),
  224. comparator_factory=MyColumnComparator)
  225. some_relationship = relationship(SomeOtherClass,
  226. comparator_factory=MyRelationshipComparator)
  227. some_composite = composite(
  228. Column("a", String), Column("b", String),
  229. comparator_factory=MyCompositeComparator
  230. )
  231. Note that for column-level operator redefinition, it's usually
  232. simpler to define the operators at the Core level, using the
  233. :attr:`.TypeEngine.comparator_factory` attribute. See
  234. :ref:`types_operators` for more detail.
  235. See also:
  236. :class:`.ColumnProperty.Comparator`
  237. :class:`.RelationshipProperty.Comparator`
  238. :class:`.CompositeProperty.Comparator`
  239. :class:`.ColumnOperators`
  240. :ref:`types_operators`
  241. :attr:`.TypeEngine.comparator_factory`
  242. """
  243. __slots__ = 'prop', 'property', '_parententity', '_adapt_to_entity'
  244. def __init__(self, prop, parentmapper, adapt_to_entity=None):
  245. self.prop = self.property = prop
  246. self._parententity = adapt_to_entity or parentmapper
  247. self._adapt_to_entity = adapt_to_entity
  248. def __clause_element__(self):
  249. raise NotImplementedError("%r" % self)
  250. def _query_clause_element(self):
  251. return self.__clause_element__()
  252. def adapt_to_entity(self, adapt_to_entity):
  253. """Return a copy of this PropComparator which will use the given
  254. :class:`.AliasedInsp` to produce corresponding expressions.
  255. """
  256. return self.__class__(self.prop, self._parententity, adapt_to_entity)
  257. @property
  258. def _parentmapper(self):
  259. """legacy; this is renamed to _parententity to be
  260. compatible with QueryableAttribute."""
  261. return inspect(self._parententity).mapper
  262. @property
  263. def adapter(self):
  264. """Produce a callable that adapts column expressions
  265. to suit an aliased version of this comparator.
  266. """
  267. if self._adapt_to_entity is None:
  268. return None
  269. else:
  270. return self._adapt_to_entity._adapt_element
  271. @property
  272. def info(self):
  273. return self.property.info
  274. @staticmethod
  275. def any_op(a, b, **kwargs):
  276. return a.any(b, **kwargs)
  277. @staticmethod
  278. def has_op(a, b, **kwargs):
  279. return a.has(b, **kwargs)
  280. @staticmethod
  281. def of_type_op(a, class_):
  282. return a.of_type(class_)
  283. def of_type(self, class_):
  284. r"""Redefine this object in terms of a polymorphic subclass.
  285. Returns a new PropComparator from which further criterion can be
  286. evaluated.
  287. e.g.::
  288. query.join(Company.employees.of_type(Engineer)).\
  289. filter(Engineer.name=='foo')
  290. :param \class_: a class or mapper indicating that criterion will be
  291. against this specific subclass.
  292. """
  293. return self.operate(PropComparator.of_type_op, class_)
  294. def any(self, criterion=None, **kwargs):
  295. r"""Return true if this collection contains any member that meets the
  296. given criterion.
  297. The usual implementation of ``any()`` is
  298. :meth:`.RelationshipProperty.Comparator.any`.
  299. :param criterion: an optional ClauseElement formulated against the
  300. member class' table or attributes.
  301. :param \**kwargs: key/value pairs corresponding to member class
  302. attribute names which will be compared via equality to the
  303. corresponding values.
  304. """
  305. return self.operate(PropComparator.any_op, criterion, **kwargs)
  306. def has(self, criterion=None, **kwargs):
  307. r"""Return true if this element references a member which meets the
  308. given criterion.
  309. The usual implementation of ``has()`` is
  310. :meth:`.RelationshipProperty.Comparator.has`.
  311. :param criterion: an optional ClauseElement formulated against the
  312. member class' table or attributes.
  313. :param \**kwargs: key/value pairs corresponding to member class
  314. attribute names which will be compared via equality to the
  315. corresponding values.
  316. """
  317. return self.operate(PropComparator.has_op, criterion, **kwargs)
  318. class StrategizedProperty(MapperProperty):
  319. """A MapperProperty which uses selectable strategies to affect
  320. loading behavior.
  321. There is a single strategy selected by default. Alternate
  322. strategies can be selected at Query time through the usage of
  323. ``StrategizedOption`` objects via the Query.options() method.
  324. The mechanics of StrategizedProperty are used for every Query
  325. invocation for every mapped attribute participating in that Query,
  326. to determine first how the attribute will be rendered in SQL
  327. and secondly how the attribute will retrieve a value from a result
  328. row and apply it to a mapped object. The routines here are very
  329. performance-critical.
  330. """
  331. __slots__ = (
  332. '_strategies', 'strategy',
  333. '_wildcard_token', '_default_path_loader_key'
  334. )
  335. strategy_wildcard_key = None
  336. def _memoized_attr__wildcard_token(self):
  337. return ("%s:%s" % (
  338. self.strategy_wildcard_key, path_registry._WILDCARD_TOKEN), )
  339. def _memoized_attr__default_path_loader_key(self):
  340. return (
  341. "loader",
  342. ("%s:%s" % (
  343. self.strategy_wildcard_key, path_registry._DEFAULT_TOKEN), )
  344. )
  345. def _get_context_loader(self, context, path):
  346. load = None
  347. # use EntityRegistry.__getitem__()->PropRegistry here so
  348. # that the path is stated in terms of our base
  349. search_path = dict.__getitem__(path, self)
  350. # search among: exact match, "attr.*", "default" strategy
  351. # if any.
  352. for path_key in (
  353. search_path._loader_key,
  354. search_path._wildcard_path_loader_key,
  355. search_path._default_path_loader_key
  356. ):
  357. if path_key in context.attributes:
  358. load = context.attributes[path_key]
  359. break
  360. return load
  361. def _get_strategy(self, key):
  362. try:
  363. return self._strategies[key]
  364. except KeyError:
  365. cls = self._strategy_lookup(*key)
  366. self._strategies[key] = self._strategies[
  367. cls] = strategy = cls(self, key)
  368. return strategy
  369. def setup(
  370. self, context, entity, path, adapter, **kwargs):
  371. loader = self._get_context_loader(context, path)
  372. if loader and loader.strategy:
  373. strat = self._get_strategy(loader.strategy)
  374. else:
  375. strat = self.strategy
  376. strat.setup_query(context, entity, path, loader, adapter, **kwargs)
  377. def create_row_processor(
  378. self, context, path, mapper,
  379. result, adapter, populators):
  380. loader = self._get_context_loader(context, path)
  381. if loader and loader.strategy:
  382. strat = self._get_strategy(loader.strategy)
  383. else:
  384. strat = self.strategy
  385. strat.create_row_processor(
  386. context, path, loader,
  387. mapper, result, adapter, populators)
  388. def do_init(self):
  389. self._strategies = {}
  390. self.strategy = self._get_strategy(self.strategy_key)
  391. def post_instrument_class(self, mapper):
  392. if not self.parent.non_primary and \
  393. not mapper.class_manager._attr_has_impl(self.key):
  394. self.strategy.init_class_attribute(mapper)
  395. _all_strategies = collections.defaultdict(dict)
  396. @classmethod
  397. def strategy_for(cls, **kw):
  398. def decorate(dec_cls):
  399. # ensure each subclass of the strategy has its
  400. # own _strategy_keys collection
  401. if '_strategy_keys' not in dec_cls.__dict__:
  402. dec_cls._strategy_keys = []
  403. key = tuple(sorted(kw.items()))
  404. cls._all_strategies[cls][key] = dec_cls
  405. dec_cls._strategy_keys.append(key)
  406. return dec_cls
  407. return decorate
  408. @classmethod
  409. def _strategy_lookup(cls, *key):
  410. for prop_cls in cls.__mro__:
  411. if prop_cls in cls._all_strategies:
  412. strategies = cls._all_strategies[prop_cls]
  413. try:
  414. return strategies[key]
  415. except KeyError:
  416. pass
  417. raise Exception("can't locate strategy for %s %s" % (cls, key))
  418. class MapperOption(object):
  419. """Describe a modification to a Query."""
  420. propagate_to_loaders = False
  421. """if True, indicate this option should be carried along
  422. to "secondary" Query objects produced during lazy loads
  423. or refresh operations.
  424. """
  425. def process_query(self, query):
  426. """Apply a modification to the given :class:`.Query`."""
  427. def process_query_conditionally(self, query):
  428. """same as process_query(), except that this option may not
  429. apply to the given query.
  430. This is typically used during a lazy load or scalar refresh
  431. operation to propagate options stated in the original Query to the
  432. new Query being used for the load. It occurs for those options that
  433. specify propagate_to_loaders=True.
  434. """
  435. self.process_query(query)
  436. class LoaderStrategy(object):
  437. """Describe the loading behavior of a StrategizedProperty object.
  438. The ``LoaderStrategy`` interacts with the querying process in three
  439. ways:
  440. * it controls the configuration of the ``InstrumentedAttribute``
  441. placed on a class to handle the behavior of the attribute. this
  442. may involve setting up class-level callable functions to fire
  443. off a select operation when the attribute is first accessed
  444. (i.e. a lazy load)
  445. * it processes the ``QueryContext`` at statement construction time,
  446. where it can modify the SQL statement that is being produced.
  447. For example, simple column attributes will add their represented
  448. column to the list of selected columns, a joined eager loader
  449. may establish join clauses to add to the statement.
  450. * It produces "row processor" functions at result fetching time.
  451. These "row processor" functions populate a particular attribute
  452. on a particular mapped instance.
  453. """
  454. __slots__ = 'parent_property', 'is_class_level', 'parent', 'key', \
  455. 'strategy_key', 'strategy_opts'
  456. def __init__(self, parent, strategy_key):
  457. self.parent_property = parent
  458. self.is_class_level = False
  459. self.parent = self.parent_property.parent
  460. self.key = self.parent_property.key
  461. self.strategy_key = strategy_key
  462. self.strategy_opts = dict(strategy_key)
  463. def init_class_attribute(self, mapper):
  464. pass
  465. def setup_query(self, context, entity, path, loadopt, adapter, **kwargs):
  466. """Establish column and other state for a given QueryContext.
  467. This method fulfills the contract specified by MapperProperty.setup().
  468. StrategizedProperty delegates its setup() method
  469. directly to this method.
  470. """
  471. def create_row_processor(self, context, path, loadopt, mapper,
  472. result, adapter, populators):
  473. """Establish row processing functions for a given QueryContext.
  474. This method fulfills the contract specified by
  475. MapperProperty.create_row_processor().
  476. StrategizedProperty delegates its create_row_processor() method
  477. directly to this method.
  478. """
  479. def __str__(self):
  480. return str(self.parent_property)