You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1991 lines
67KB

  1. # orm/util.py
  2. # Copyright (C) 2005-2021 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. import re
  8. import types
  9. import weakref
  10. from . import attributes # noqa
  11. from .base import _class_to_mapper # noqa
  12. from .base import _never_set # noqa
  13. from .base import _none_set # noqa
  14. from .base import attribute_str # noqa
  15. from .base import class_mapper # noqa
  16. from .base import InspectionAttr # noqa
  17. from .base import instance_str # noqa
  18. from .base import object_mapper # noqa
  19. from .base import object_state # noqa
  20. from .base import state_attribute_str # noqa
  21. from .base import state_class_str # noqa
  22. from .base import state_str # noqa
  23. from .interfaces import CriteriaOption
  24. from .interfaces import MapperProperty # noqa
  25. from .interfaces import ORMColumnsClauseRole
  26. from .interfaces import ORMEntityColumnsClauseRole
  27. from .interfaces import ORMFromClauseRole
  28. from .interfaces import PropComparator # noqa
  29. from .path_registry import PathRegistry # noqa
  30. from .. import event
  31. from .. import exc as sa_exc
  32. from .. import inspection
  33. from .. import sql
  34. from .. import util
  35. from ..engine.result import result_tuple
  36. from ..sql import base as sql_base
  37. from ..sql import coercions
  38. from ..sql import expression
  39. from ..sql import lambdas
  40. from ..sql import roles
  41. from ..sql import util as sql_util
  42. from ..sql import visitors
  43. from ..sql.annotation import SupportsCloneAnnotations
  44. from ..sql.base import ColumnCollection
  45. all_cascades = frozenset(
  46. (
  47. "delete",
  48. "delete-orphan",
  49. "all",
  50. "merge",
  51. "expunge",
  52. "save-update",
  53. "refresh-expire",
  54. "none",
  55. )
  56. )
  57. class CascadeOptions(frozenset):
  58. """Keeps track of the options sent to
  59. :paramref:`.relationship.cascade`"""
  60. _add_w_all_cascades = all_cascades.difference(
  61. ["all", "none", "delete-orphan"]
  62. )
  63. _allowed_cascades = all_cascades
  64. _viewonly_cascades = ["expunge", "all", "none", "refresh-expire"]
  65. __slots__ = (
  66. "save_update",
  67. "delete",
  68. "refresh_expire",
  69. "merge",
  70. "expunge",
  71. "delete_orphan",
  72. )
  73. def __new__(cls, value_list):
  74. if isinstance(value_list, util.string_types) or value_list is None:
  75. return cls.from_string(value_list)
  76. values = set(value_list)
  77. if values.difference(cls._allowed_cascades):
  78. raise sa_exc.ArgumentError(
  79. "Invalid cascade option(s): %s"
  80. % ", ".join(
  81. [
  82. repr(x)
  83. for x in sorted(
  84. values.difference(cls._allowed_cascades)
  85. )
  86. ]
  87. )
  88. )
  89. if "all" in values:
  90. values.update(cls._add_w_all_cascades)
  91. if "none" in values:
  92. values.clear()
  93. values.discard("all")
  94. self = frozenset.__new__(CascadeOptions, values)
  95. self.save_update = "save-update" in values
  96. self.delete = "delete" in values
  97. self.refresh_expire = "refresh-expire" in values
  98. self.merge = "merge" in values
  99. self.expunge = "expunge" in values
  100. self.delete_orphan = "delete-orphan" in values
  101. if self.delete_orphan and not self.delete:
  102. util.warn(
  103. "The 'delete-orphan' cascade " "option requires 'delete'."
  104. )
  105. return self
  106. def __repr__(self):
  107. return "CascadeOptions(%r)" % (",".join([x for x in sorted(self)]))
  108. @classmethod
  109. def from_string(cls, arg):
  110. values = [c for c in re.split(r"\s*,\s*", arg or "") if c]
  111. return cls(values)
  112. def _validator_events(desc, key, validator, include_removes, include_backrefs):
  113. """Runs a validation method on an attribute value to be set or
  114. appended.
  115. """
  116. if not include_backrefs:
  117. def detect_is_backref(state, initiator):
  118. impl = state.manager[key].impl
  119. return initiator.impl is not impl
  120. if include_removes:
  121. def append(state, value, initiator):
  122. if initiator.op is not attributes.OP_BULK_REPLACE and (
  123. include_backrefs or not detect_is_backref(state, initiator)
  124. ):
  125. return validator(state.obj(), key, value, False)
  126. else:
  127. return value
  128. def bulk_set(state, values, initiator):
  129. if include_backrefs or not detect_is_backref(state, initiator):
  130. obj = state.obj()
  131. values[:] = [
  132. validator(obj, key, value, False) for value in values
  133. ]
  134. def set_(state, value, oldvalue, initiator):
  135. if include_backrefs or not detect_is_backref(state, initiator):
  136. return validator(state.obj(), key, value, False)
  137. else:
  138. return value
  139. def remove(state, value, initiator):
  140. if include_backrefs or not detect_is_backref(state, initiator):
  141. validator(state.obj(), key, value, True)
  142. else:
  143. def append(state, value, initiator):
  144. if initiator.op is not attributes.OP_BULK_REPLACE and (
  145. include_backrefs or not detect_is_backref(state, initiator)
  146. ):
  147. return validator(state.obj(), key, value)
  148. else:
  149. return value
  150. def bulk_set(state, values, initiator):
  151. if include_backrefs or not detect_is_backref(state, initiator):
  152. obj = state.obj()
  153. values[:] = [validator(obj, key, value) for value in values]
  154. def set_(state, value, oldvalue, initiator):
  155. if include_backrefs or not detect_is_backref(state, initiator):
  156. return validator(state.obj(), key, value)
  157. else:
  158. return value
  159. event.listen(desc, "append", append, raw=True, retval=True)
  160. event.listen(desc, "bulk_replace", bulk_set, raw=True)
  161. event.listen(desc, "set", set_, raw=True, retval=True)
  162. if include_removes:
  163. event.listen(desc, "remove", remove, raw=True, retval=True)
  164. def polymorphic_union(
  165. table_map, typecolname, aliasname="p_union", cast_nulls=True
  166. ):
  167. """Create a ``UNION`` statement used by a polymorphic mapper.
  168. See :ref:`concrete_inheritance` for an example of how
  169. this is used.
  170. :param table_map: mapping of polymorphic identities to
  171. :class:`_schema.Table` objects.
  172. :param typecolname: string name of a "discriminator" column, which will be
  173. derived from the query, producing the polymorphic identity for
  174. each row. If ``None``, no polymorphic discriminator is generated.
  175. :param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
  176. construct generated.
  177. :param cast_nulls: if True, non-existent columns, which are represented
  178. as labeled NULLs, will be passed into CAST. This is a legacy behavior
  179. that is problematic on some backends such as Oracle - in which case it
  180. can be set to False.
  181. """
  182. colnames = util.OrderedSet()
  183. colnamemaps = {}
  184. types = {}
  185. for key in table_map:
  186. table = table_map[key]
  187. table = coercions.expect(
  188. roles.StrictFromClauseRole, table, allow_select=True
  189. )
  190. table_map[key] = table
  191. m = {}
  192. for c in table.c:
  193. if c.key == typecolname:
  194. raise sa_exc.InvalidRequestError(
  195. "Polymorphic union can't use '%s' as the discriminator "
  196. "column due to mapped column %r; please apply the "
  197. "'typecolname' "
  198. "argument; this is available on "
  199. "ConcreteBase as '_concrete_discriminator_name'"
  200. % (typecolname, c)
  201. )
  202. colnames.add(c.key)
  203. m[c.key] = c
  204. types[c.key] = c.type
  205. colnamemaps[table] = m
  206. def col(name, table):
  207. try:
  208. return colnamemaps[table][name]
  209. except KeyError:
  210. if cast_nulls:
  211. return sql.cast(sql.null(), types[name]).label(name)
  212. else:
  213. return sql.type_coerce(sql.null(), types[name]).label(name)
  214. result = []
  215. for type_, table in table_map.items():
  216. if typecolname is not None:
  217. result.append(
  218. sql.select(
  219. *(
  220. [col(name, table) for name in colnames]
  221. + [
  222. sql.literal_column(
  223. sql_util._quote_ddl_expr(type_)
  224. ).label(typecolname)
  225. ]
  226. )
  227. ).select_from(table)
  228. )
  229. else:
  230. result.append(
  231. sql.select(
  232. *[col(name, table) for name in colnames]
  233. ).select_from(table)
  234. )
  235. return sql.union_all(*result).alias(aliasname)
  236. def identity_key(*args, **kwargs):
  237. r"""Generate "identity key" tuples, as are used as keys in the
  238. :attr:`.Session.identity_map` dictionary.
  239. This function has several call styles:
  240. * ``identity_key(class, ident, identity_token=token)``
  241. This form receives a mapped class and a primary key scalar or
  242. tuple as an argument.
  243. E.g.::
  244. >>> identity_key(MyClass, (1, 2))
  245. (<class '__main__.MyClass'>, (1, 2), None)
  246. :param class: mapped class (must be a positional argument)
  247. :param ident: primary key, may be a scalar or tuple argument.
  248. :param identity_token: optional identity token
  249. .. versionadded:: 1.2 added identity_token
  250. * ``identity_key(instance=instance)``
  251. This form will produce the identity key for a given instance. The
  252. instance need not be persistent, only that its primary key attributes
  253. are populated (else the key will contain ``None`` for those missing
  254. values).
  255. E.g.::
  256. >>> instance = MyClass(1, 2)
  257. >>> identity_key(instance=instance)
  258. (<class '__main__.MyClass'>, (1, 2), None)
  259. In this form, the given instance is ultimately run though
  260. :meth:`_orm.Mapper.identity_key_from_instance`, which will have the
  261. effect of performing a database check for the corresponding row
  262. if the object is expired.
  263. :param instance: object instance (must be given as a keyword arg)
  264. * ``identity_key(class, row=row, identity_token=token)``
  265. This form is similar to the class/tuple form, except is passed a
  266. database result row as a :class:`.Row` object.
  267. E.g.::
  268. >>> row = engine.execute(\
  269. text("select * from table where a=1 and b=2")\
  270. ).first()
  271. >>> identity_key(MyClass, row=row)
  272. (<class '__main__.MyClass'>, (1, 2), None)
  273. :param class: mapped class (must be a positional argument)
  274. :param row: :class:`.Row` row returned by a :class:`_engine.CursorResult`
  275. (must be given as a keyword arg)
  276. :param identity_token: optional identity token
  277. .. versionadded:: 1.2 added identity_token
  278. """
  279. if args:
  280. row = None
  281. largs = len(args)
  282. if largs == 1:
  283. class_ = args[0]
  284. try:
  285. row = kwargs.pop("row")
  286. except KeyError:
  287. ident = kwargs.pop("ident")
  288. elif largs in (2, 3):
  289. class_, ident = args
  290. else:
  291. raise sa_exc.ArgumentError(
  292. "expected up to three positional arguments, " "got %s" % largs
  293. )
  294. identity_token = kwargs.pop("identity_token", None)
  295. if kwargs:
  296. raise sa_exc.ArgumentError(
  297. "unknown keyword arguments: %s" % ", ".join(kwargs)
  298. )
  299. mapper = class_mapper(class_)
  300. if row is None:
  301. return mapper.identity_key_from_primary_key(
  302. util.to_list(ident), identity_token=identity_token
  303. )
  304. else:
  305. return mapper.identity_key_from_row(
  306. row, identity_token=identity_token
  307. )
  308. else:
  309. instance = kwargs.pop("instance")
  310. if kwargs:
  311. raise sa_exc.ArgumentError(
  312. "unknown keyword arguments: %s" % ", ".join(kwargs.keys)
  313. )
  314. mapper = object_mapper(instance)
  315. return mapper.identity_key_from_instance(instance)
  316. class ORMAdapter(sql_util.ColumnAdapter):
  317. """ColumnAdapter subclass which excludes adaptation of entities from
  318. non-matching mappers.
  319. """
  320. def __init__(
  321. self,
  322. entity,
  323. equivalents=None,
  324. adapt_required=False,
  325. allow_label_resolve=True,
  326. anonymize_labels=False,
  327. ):
  328. info = inspection.inspect(entity)
  329. self.mapper = info.mapper
  330. selectable = info.selectable
  331. is_aliased_class = info.is_aliased_class
  332. if is_aliased_class:
  333. self.aliased_class = entity
  334. else:
  335. self.aliased_class = None
  336. sql_util.ColumnAdapter.__init__(
  337. self,
  338. selectable,
  339. equivalents,
  340. adapt_required=adapt_required,
  341. allow_label_resolve=allow_label_resolve,
  342. anonymize_labels=anonymize_labels,
  343. include_fn=self._include_fn,
  344. )
  345. def _include_fn(self, elem):
  346. entity = elem._annotations.get("parentmapper", None)
  347. return not entity or entity.isa(self.mapper)
  348. class AliasedClass(object):
  349. r"""Represents an "aliased" form of a mapped class for usage with Query.
  350. The ORM equivalent of a :func:`~sqlalchemy.sql.expression.alias`
  351. construct, this object mimics the mapped class using a
  352. ``__getattr__`` scheme and maintains a reference to a
  353. real :class:`~sqlalchemy.sql.expression.Alias` object.
  354. A primary purpose of :class:`.AliasedClass` is to serve as an alternate
  355. within a SQL statement generated by the ORM, such that an existing
  356. mapped entity can be used in multiple contexts. A simple example::
  357. # find all pairs of users with the same name
  358. user_alias = aliased(User)
  359. session.query(User, user_alias).\
  360. join((user_alias, User.id > user_alias.id)).\
  361. filter(User.name == user_alias.name)
  362. :class:`.AliasedClass` is also capable of mapping an existing mapped
  363. class to an entirely new selectable, provided this selectable is column-
  364. compatible with the existing mapped selectable, and it can also be
  365. configured in a mapping as the target of a :func:`_orm.relationship`.
  366. See the links below for examples.
  367. The :class:`.AliasedClass` object is constructed typically using the
  368. :func:`_orm.aliased` function. It also is produced with additional
  369. configuration when using the :func:`_orm.with_polymorphic` function.
  370. The resulting object is an instance of :class:`.AliasedClass`.
  371. This object implements an attribute scheme which produces the
  372. same attribute and method interface as the original mapped
  373. class, allowing :class:`.AliasedClass` to be compatible
  374. with any attribute technique which works on the original class,
  375. including hybrid attributes (see :ref:`hybrids_toplevel`).
  376. The :class:`.AliasedClass` can be inspected for its underlying
  377. :class:`_orm.Mapper`, aliased selectable, and other information
  378. using :func:`_sa.inspect`::
  379. from sqlalchemy import inspect
  380. my_alias = aliased(MyClass)
  381. insp = inspect(my_alias)
  382. The resulting inspection object is an instance of :class:`.AliasedInsp`.
  383. .. seealso::
  384. :func:`.aliased`
  385. :func:`.with_polymorphic`
  386. :ref:`relationship_aliased_class`
  387. :ref:`relationship_to_window_function`
  388. """
  389. def __init__(
  390. self,
  391. mapped_class_or_ac,
  392. alias=None,
  393. name=None,
  394. flat=False,
  395. adapt_on_names=False,
  396. # TODO: None for default here?
  397. with_polymorphic_mappers=(),
  398. with_polymorphic_discriminator=None,
  399. base_alias=None,
  400. use_mapper_path=False,
  401. represents_outer_join=False,
  402. ):
  403. insp = inspection.inspect(mapped_class_or_ac)
  404. mapper = insp.mapper
  405. if alias is None:
  406. alias = mapper._with_polymorphic_selectable._anonymous_fromclause(
  407. name=name,
  408. flat=flat,
  409. )
  410. self._aliased_insp = AliasedInsp(
  411. self,
  412. insp,
  413. alias,
  414. name,
  415. with_polymorphic_mappers
  416. if with_polymorphic_mappers
  417. else mapper.with_polymorphic_mappers,
  418. with_polymorphic_discriminator
  419. if with_polymorphic_discriminator is not None
  420. else mapper.polymorphic_on,
  421. base_alias,
  422. use_mapper_path,
  423. adapt_on_names,
  424. represents_outer_join,
  425. )
  426. self.__name__ = "AliasedClass_%s" % mapper.class_.__name__
  427. @classmethod
  428. def _reconstitute_from_aliased_insp(cls, aliased_insp):
  429. obj = cls.__new__(cls)
  430. obj.__name__ = "AliasedClass_%s" % aliased_insp.mapper.class_.__name__
  431. obj._aliased_insp = aliased_insp
  432. if aliased_insp._is_with_polymorphic:
  433. for sub_aliased_insp in aliased_insp._with_polymorphic_entities:
  434. if sub_aliased_insp is not aliased_insp:
  435. ent = AliasedClass._reconstitute_from_aliased_insp(
  436. sub_aliased_insp
  437. )
  438. setattr(obj, sub_aliased_insp.class_.__name__, ent)
  439. return obj
  440. def __getattr__(self, key):
  441. try:
  442. _aliased_insp = self.__dict__["_aliased_insp"]
  443. except KeyError:
  444. raise AttributeError()
  445. else:
  446. target = _aliased_insp._target
  447. # maintain all getattr mechanics
  448. attr = getattr(target, key)
  449. # attribute is a method, that will be invoked against a
  450. # "self"; so just return a new method with the same function and
  451. # new self
  452. if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
  453. return types.MethodType(attr.__func__, self)
  454. # attribute is a descriptor, that will be invoked against a
  455. # "self"; so invoke the descriptor against this self
  456. if hasattr(attr, "__get__"):
  457. attr = attr.__get__(None, self)
  458. # attributes within the QueryableAttribute system will want this
  459. # to be invoked so the object can be adapted
  460. if hasattr(attr, "adapt_to_entity"):
  461. attr = attr.adapt_to_entity(_aliased_insp)
  462. setattr(self, key, attr)
  463. return attr
  464. def _get_from_serialized(self, key, mapped_class, aliased_insp):
  465. # this method is only used in terms of the
  466. # sqlalchemy.ext.serializer extension
  467. attr = getattr(mapped_class, key)
  468. if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
  469. return types.MethodType(attr.__func__, self)
  470. # attribute is a descriptor, that will be invoked against a
  471. # "self"; so invoke the descriptor against this self
  472. if hasattr(attr, "__get__"):
  473. attr = attr.__get__(None, self)
  474. # attributes within the QueryableAttribute system will want this
  475. # to be invoked so the object can be adapted
  476. if hasattr(attr, "adapt_to_entity"):
  477. aliased_insp._weak_entity = weakref.ref(self)
  478. attr = attr.adapt_to_entity(aliased_insp)
  479. setattr(self, key, attr)
  480. return attr
  481. def __repr__(self):
  482. return "<AliasedClass at 0x%x; %s>" % (
  483. id(self),
  484. self._aliased_insp._target.__name__,
  485. )
  486. def __str__(self):
  487. return str(self._aliased_insp)
  488. class AliasedInsp(
  489. ORMEntityColumnsClauseRole,
  490. ORMFromClauseRole,
  491. sql_base.MemoizedHasCacheKey,
  492. InspectionAttr,
  493. ):
  494. """Provide an inspection interface for an
  495. :class:`.AliasedClass` object.
  496. The :class:`.AliasedInsp` object is returned
  497. given an :class:`.AliasedClass` using the
  498. :func:`_sa.inspect` function::
  499. from sqlalchemy import inspect
  500. from sqlalchemy.orm import aliased
  501. my_alias = aliased(MyMappedClass)
  502. insp = inspect(my_alias)
  503. Attributes on :class:`.AliasedInsp`
  504. include:
  505. * ``entity`` - the :class:`.AliasedClass` represented.
  506. * ``mapper`` - the :class:`_orm.Mapper` mapping the underlying class.
  507. * ``selectable`` - the :class:`_expression.Alias`
  508. construct which ultimately
  509. represents an aliased :class:`_schema.Table` or
  510. :class:`_expression.Select`
  511. construct.
  512. * ``name`` - the name of the alias. Also is used as the attribute
  513. name when returned in a result tuple from :class:`_query.Query`.
  514. * ``with_polymorphic_mappers`` - collection of :class:`_orm.Mapper`
  515. objects
  516. indicating all those mappers expressed in the select construct
  517. for the :class:`.AliasedClass`.
  518. * ``polymorphic_on`` - an alternate column or SQL expression which
  519. will be used as the "discriminator" for a polymorphic load.
  520. .. seealso::
  521. :ref:`inspection_toplevel`
  522. """
  523. def __init__(
  524. self,
  525. entity,
  526. inspected,
  527. selectable,
  528. name,
  529. with_polymorphic_mappers,
  530. polymorphic_on,
  531. _base_alias,
  532. _use_mapper_path,
  533. adapt_on_names,
  534. represents_outer_join,
  535. ):
  536. mapped_class_or_ac = inspected.entity
  537. mapper = inspected.mapper
  538. self._weak_entity = weakref.ref(entity)
  539. self.mapper = mapper
  540. self.selectable = (
  541. self.persist_selectable
  542. ) = self.local_table = selectable
  543. self.name = name
  544. self.polymorphic_on = polymorphic_on
  545. self._base_alias = weakref.ref(_base_alias or self)
  546. self._use_mapper_path = _use_mapper_path
  547. self.represents_outer_join = represents_outer_join
  548. if with_polymorphic_mappers:
  549. self._is_with_polymorphic = True
  550. self.with_polymorphic_mappers = with_polymorphic_mappers
  551. self._with_polymorphic_entities = []
  552. for poly in self.with_polymorphic_mappers:
  553. if poly is not mapper:
  554. ent = AliasedClass(
  555. poly.class_,
  556. selectable,
  557. base_alias=self,
  558. adapt_on_names=adapt_on_names,
  559. use_mapper_path=_use_mapper_path,
  560. )
  561. setattr(self.entity, poly.class_.__name__, ent)
  562. self._with_polymorphic_entities.append(ent._aliased_insp)
  563. else:
  564. self._is_with_polymorphic = False
  565. self.with_polymorphic_mappers = [mapper]
  566. self._adapter = sql_util.ColumnAdapter(
  567. selectable,
  568. equivalents=mapper._equivalent_columns,
  569. adapt_on_names=adapt_on_names,
  570. anonymize_labels=True,
  571. # make sure the adapter doesn't try to grab other tables that
  572. # are not even the thing we are mapping, such as embedded
  573. # selectables in subqueries or CTEs. See issue #6060
  574. adapt_from_selectables=[
  575. m.selectable for m in self.with_polymorphic_mappers
  576. ],
  577. )
  578. if inspected.is_aliased_class:
  579. self._adapter = inspected._adapter.wrap(self._adapter)
  580. self._adapt_on_names = adapt_on_names
  581. self._target = mapped_class_or_ac
  582. # self._target = mapper.class_ # mapped_class_or_ac
  583. @property
  584. def entity(self):
  585. # to eliminate reference cycles, the AliasedClass is held weakly.
  586. # this produces some situations where the AliasedClass gets lost,
  587. # particularly when one is created internally and only the AliasedInsp
  588. # is passed around.
  589. # to work around this case, we just generate a new one when we need
  590. # it, as it is a simple class with very little initial state on it.
  591. ent = self._weak_entity()
  592. if ent is None:
  593. ent = AliasedClass._reconstitute_from_aliased_insp(self)
  594. self._weak_entity = weakref.ref(ent)
  595. return ent
  596. is_aliased_class = True
  597. "always returns True"
  598. @util.memoized_instancemethod
  599. def __clause_element__(self):
  600. return self.selectable._annotate(
  601. {
  602. "parentmapper": self.mapper,
  603. "parententity": self,
  604. "entity_namespace": self,
  605. }
  606. )._set_propagate_attrs(
  607. {"compile_state_plugin": "orm", "plugin_subject": self}
  608. )
  609. @property
  610. def entity_namespace(self):
  611. return self.entity
  612. _cache_key_traversal = [
  613. ("name", visitors.ExtendedInternalTraversal.dp_string),
  614. ("_adapt_on_names", visitors.ExtendedInternalTraversal.dp_boolean),
  615. ("selectable", visitors.ExtendedInternalTraversal.dp_clauseelement),
  616. ]
  617. @property
  618. def class_(self):
  619. """Return the mapped class ultimately represented by this
  620. :class:`.AliasedInsp`."""
  621. return self.mapper.class_
  622. @property
  623. def _path_registry(self):
  624. if self._use_mapper_path:
  625. return self.mapper._path_registry
  626. else:
  627. return PathRegistry.per_mapper(self)
  628. def __getstate__(self):
  629. return {
  630. "entity": self.entity,
  631. "mapper": self.mapper,
  632. "alias": self.selectable,
  633. "name": self.name,
  634. "adapt_on_names": self._adapt_on_names,
  635. "with_polymorphic_mappers": self.with_polymorphic_mappers,
  636. "with_polymorphic_discriminator": self.polymorphic_on,
  637. "base_alias": self._base_alias(),
  638. "use_mapper_path": self._use_mapper_path,
  639. "represents_outer_join": self.represents_outer_join,
  640. }
  641. def __setstate__(self, state):
  642. self.__init__(
  643. state["entity"],
  644. state["mapper"],
  645. state["alias"],
  646. state["name"],
  647. state["with_polymorphic_mappers"],
  648. state["with_polymorphic_discriminator"],
  649. state["base_alias"],
  650. state["use_mapper_path"],
  651. state["adapt_on_names"],
  652. state["represents_outer_join"],
  653. )
  654. def _adapt_element(self, elem, key=None):
  655. d = {
  656. "parententity": self,
  657. "parentmapper": self.mapper,
  658. }
  659. if key:
  660. d["proxy_key"] = key
  661. return (
  662. self._adapter.traverse(elem)
  663. ._annotate(d)
  664. ._set_propagate_attrs(
  665. {"compile_state_plugin": "orm", "plugin_subject": self}
  666. )
  667. )
  668. def _entity_for_mapper(self, mapper):
  669. self_poly = self.with_polymorphic_mappers
  670. if mapper in self_poly:
  671. if mapper is self.mapper:
  672. return self
  673. else:
  674. return getattr(
  675. self.entity, mapper.class_.__name__
  676. )._aliased_insp
  677. elif mapper.isa(self.mapper):
  678. return self
  679. else:
  680. assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
  681. @util.memoized_property
  682. def _get_clause(self):
  683. onclause, replacemap = self.mapper._get_clause
  684. return (
  685. self._adapter.traverse(onclause),
  686. {
  687. self._adapter.traverse(col): param
  688. for col, param in replacemap.items()
  689. },
  690. )
  691. @util.memoized_property
  692. def _memoized_values(self):
  693. return {}
  694. @util.memoized_property
  695. def _all_column_expressions(self):
  696. if self._is_with_polymorphic:
  697. cols_plus_keys = self.mapper._columns_plus_keys(
  698. [ent.mapper for ent in self._with_polymorphic_entities]
  699. )
  700. else:
  701. cols_plus_keys = self.mapper._columns_plus_keys()
  702. cols_plus_keys = [
  703. (key, self._adapt_element(col)) for key, col in cols_plus_keys
  704. ]
  705. return ColumnCollection(cols_plus_keys)
  706. def _memo(self, key, callable_, *args, **kw):
  707. if key in self._memoized_values:
  708. return self._memoized_values[key]
  709. else:
  710. self._memoized_values[key] = value = callable_(*args, **kw)
  711. return value
  712. def __repr__(self):
  713. if self.with_polymorphic_mappers:
  714. with_poly = "(%s)" % ", ".join(
  715. mp.class_.__name__ for mp in self.with_polymorphic_mappers
  716. )
  717. else:
  718. with_poly = ""
  719. return "<AliasedInsp at 0x%x; %s%s>" % (
  720. id(self),
  721. self.class_.__name__,
  722. with_poly,
  723. )
  724. def __str__(self):
  725. if self._is_with_polymorphic:
  726. return "with_polymorphic(%s, [%s])" % (
  727. self._target.__name__,
  728. ", ".join(
  729. mp.class_.__name__
  730. for mp in self.with_polymorphic_mappers
  731. if mp is not self.mapper
  732. ),
  733. )
  734. else:
  735. return "aliased(%s)" % (self._target.__name__,)
  736. class _WrapUserEntity(object):
  737. """A wrapper used within the loader_criteria lambda caller so that
  738. we can bypass declared_attr descriptors on unmapped mixins, which
  739. normally emit a warning for such use.
  740. might also be useful for other per-lambda instrumentations should
  741. the need arise.
  742. """
  743. def __init__(self, subject):
  744. self.subject = subject
  745. @util.preload_module("sqlalchemy.orm.decl_api")
  746. def __getattribute__(self, name):
  747. decl_api = util.preloaded.orm.decl_api
  748. subject = object.__getattribute__(self, "subject")
  749. if name in subject.__dict__ and isinstance(
  750. subject.__dict__[name], decl_api.declared_attr
  751. ):
  752. return subject.__dict__[name].fget(subject)
  753. else:
  754. return getattr(subject, name)
  755. class LoaderCriteriaOption(CriteriaOption):
  756. """Add additional WHERE criteria to the load for all occurrences of
  757. a particular entity.
  758. :class:`_orm.LoaderCriteriaOption` is invoked using the
  759. :func:`_orm.with_loader_criteria` function; see that function for
  760. details.
  761. .. versionadded:: 1.4
  762. """
  763. _traverse_internals = [
  764. ("root_entity", visitors.ExtendedInternalTraversal.dp_plain_obj),
  765. ("entity", visitors.ExtendedInternalTraversal.dp_has_cache_key),
  766. ("where_criteria", visitors.InternalTraversal.dp_clauseelement),
  767. ("include_aliases", visitors.InternalTraversal.dp_boolean),
  768. ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
  769. ]
  770. def __init__(
  771. self,
  772. entity_or_base,
  773. where_criteria,
  774. loader_only=False,
  775. include_aliases=False,
  776. propagate_to_loaders=True,
  777. track_closure_variables=True,
  778. ):
  779. """Add additional WHERE criteria to the load for all occurrences of
  780. a particular entity.
  781. .. versionadded:: 1.4
  782. The :func:`_orm.with_loader_criteria` option is intended to add
  783. limiting criteria to a particular kind of entity in a query,
  784. **globally**, meaning it will apply to the entity as it appears
  785. in the SELECT query as well as within any subqueries, join
  786. conditions, and relationship loads, including both eager and lazy
  787. loaders, without the need for it to be specified in any particular
  788. part of the query. The rendering logic uses the same system used by
  789. single table inheritance to ensure a certain discriminator is applied
  790. to a table.
  791. E.g., using :term:`2.0-style` queries, we can limit the way the
  792. ``User.addresses`` collection is loaded, regardless of the kind
  793. of loading used::
  794. from sqlalchemy.orm import with_loader_criteria
  795. stmt = select(User).options(
  796. selectinload(User.addresses),
  797. with_loader_criteria(Address, Address.email_address != 'foo'))
  798. )
  799. Above, the "selectinload" for ``User.addresses`` will apply the
  800. given filtering criteria to the WHERE clause.
  801. Another example, where the filtering will be applied to the
  802. ON clause of the join, in this example using :term:`1.x style`
  803. queries::
  804. q = session.query(User).outerjoin(User.addresses).options(
  805. with_loader_criteria(Address, Address.email_address != 'foo'))
  806. )
  807. The primary purpose of :func:`_orm.with_loader_criteria` is to use
  808. it in the :meth:`_orm.SessionEvents.do_orm_execute` event handler
  809. to ensure that all occurrences of a particular entity are filtered
  810. in a certain way, such as filtering for access control roles. It
  811. also can be used to apply criteria to relationship loads. In the
  812. example below, we can apply a certain set of rules to all queries
  813. emitted by a particular :class:`_orm.Session`::
  814. session = Session(bind=engine)
  815. @event.listens_for("do_orm_execute", session)
  816. def _add_filtering_criteria(execute_state):
  817. if (
  818. execute_state.is_select
  819. and not execute_state.is_column_load
  820. and not execute_state.is_relationship_load
  821. ):
  822. execute_state.statement = execute_state.statement.options(
  823. with_loader_criteria(
  824. SecurityRole,
  825. lambda cls: cls.role.in_(['some_role']),
  826. include_aliases=True
  827. )
  828. )
  829. In the above example, the :meth:`_orm.SessionEvents.do_orm_execute`
  830. event will intercept all queries emitted using the
  831. :class:`_orm.Session`. For those queries which are SELECT statements
  832. and are not attribute or relationship loads a custom
  833. :func:`_orm.with_loader_criteria` option is added to the query. The
  834. :func:`_orm.with_loader_criteria` option will be used in the given
  835. statement and will also be automatically propagated to all relationship
  836. loads that descend from this query.
  837. The criteria argument given is a ``lambda`` that accepts a ``cls``
  838. argument. The given class will expand to include all mapped subclass
  839. and need not itself be a mapped class.
  840. .. warning:: The use of a lambda inside of the call to
  841. :func:`_orm.with_loader_criteria` is only invoked **once per unique
  842. class**. Custom functions should not be invoked within this lambda.
  843. See :ref:`engine_lambda_caching` for an overview of the "lambda SQL"
  844. feature, which is for advanced use only.
  845. :param entity_or_base: a mapped class, or a class that is a super
  846. class of a particular set of mapped classes, to which the rule
  847. will apply.
  848. :param where_criteria: a Core SQL expression that applies limiting
  849. criteria. This may also be a "lambda:" or Python function that
  850. accepts a target class as an argument, when the given class is
  851. a base with many different mapped subclasses.
  852. :param include_aliases: if True, apply the rule to :func:`_orm.aliased`
  853. constructs as well.
  854. :param propagate_to_loaders: defaults to True, apply to relationship
  855. loaders such as lazy loaders.
  856. .. seealso::
  857. :ref:`examples_session_orm_events` - includes examples of using
  858. :func:`_orm.with_loader_criteria`.
  859. :ref:`do_orm_execute_global_criteria` - basic example on how to
  860. combine :func:`_orm.with_loader_criteria` with the
  861. :meth:`_orm.SessionEvents.do_orm_execute` event.
  862. :param track_closure_variables: when False, closure variables inside
  863. of a lambda expression will not be used as part of
  864. any cache key. This allows more complex expressions to be used
  865. inside of a lambda expression but requires that the lambda ensures
  866. it returns the identical SQL every time given a particular class.
  867. .. versionadded:: 1.4.0b2
  868. """
  869. entity = inspection.inspect(entity_or_base, False)
  870. if entity is None:
  871. self.root_entity = entity_or_base
  872. self.entity = None
  873. else:
  874. self.root_entity = None
  875. self.entity = entity
  876. if callable(where_criteria):
  877. self.deferred_where_criteria = True
  878. self.where_criteria = lambdas.DeferredLambdaElement(
  879. where_criteria,
  880. roles.WhereHavingRole,
  881. lambda_args=(
  882. _WrapUserEntity(
  883. self.root_entity
  884. if self.root_entity is not None
  885. else self.entity.entity,
  886. ),
  887. ),
  888. opts=lambdas.LambdaOptions(
  889. track_closure_variables=track_closure_variables
  890. ),
  891. )
  892. else:
  893. self.deferred_where_criteria = False
  894. self.where_criteria = coercions.expect(
  895. roles.WhereHavingRole, where_criteria
  896. )
  897. self.include_aliases = include_aliases
  898. self.propagate_to_loaders = propagate_to_loaders
  899. def _all_mappers(self):
  900. if self.entity:
  901. for ent in self.entity.mapper.self_and_descendants:
  902. yield ent
  903. else:
  904. stack = list(self.root_entity.__subclasses__())
  905. while stack:
  906. subclass = stack.pop(0)
  907. ent = inspection.inspect(subclass, raiseerr=False)
  908. if ent:
  909. for mp in ent.mapper.self_and_descendants:
  910. yield mp
  911. else:
  912. stack.extend(subclass.__subclasses__())
  913. def _resolve_where_criteria(self, ext_info):
  914. if self.deferred_where_criteria:
  915. return self.where_criteria._resolve_with_args(ext_info.entity)
  916. else:
  917. return self.where_criteria
  918. def process_compile_state(self, compile_state):
  919. """Apply a modification to a given :class:`.CompileState`."""
  920. # if options to limit the criteria to immediate query only,
  921. # use compile_state.attributes instead
  922. if compile_state.compile_options._with_polymorphic_adapt_map:
  923. util.warn(
  924. "The with_loader_criteria() function may not work "
  925. "correctly with the legacy Query.with_polymorphic() feature. "
  926. "Please migrate code to use the with_polymorphic() standalone "
  927. "function before using with_loader_criteria()."
  928. )
  929. if not compile_state.compile_options._for_refresh_state:
  930. self.get_global_criteria(compile_state.global_attributes)
  931. def get_global_criteria(self, attributes):
  932. for mp in self._all_mappers():
  933. load_criteria = attributes.setdefault(
  934. ("additional_entity_criteria", mp), []
  935. )
  936. load_criteria.append(self)
  937. inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
  938. inspection._inspects(AliasedInsp)(lambda target: target)
  939. def aliased(element, alias=None, name=None, flat=False, adapt_on_names=False):
  940. """Produce an alias of the given element, usually an :class:`.AliasedClass`
  941. instance.
  942. E.g.::
  943. my_alias = aliased(MyClass)
  944. session.query(MyClass, my_alias).filter(MyClass.id > my_alias.id)
  945. The :func:`.aliased` function is used to create an ad-hoc mapping of a
  946. mapped class to a new selectable. By default, a selectable is generated
  947. from the normally mapped selectable (typically a :class:`_schema.Table`
  948. ) using the
  949. :meth:`_expression.FromClause.alias` method. However, :func:`.aliased`
  950. can also be
  951. used to link the class to a new :func:`_expression.select` statement.
  952. Also, the :func:`.with_polymorphic` function is a variant of
  953. :func:`.aliased` that is intended to specify a so-called "polymorphic
  954. selectable", that corresponds to the union of several joined-inheritance
  955. subclasses at once.
  956. For convenience, the :func:`.aliased` function also accepts plain
  957. :class:`_expression.FromClause` constructs, such as a
  958. :class:`_schema.Table` or
  959. :func:`_expression.select` construct. In those cases, the
  960. :meth:`_expression.FromClause.alias`
  961. method is called on the object and the new
  962. :class:`_expression.Alias` object returned. The returned
  963. :class:`_expression.Alias` is not
  964. ORM-mapped in this case.
  965. :param element: element to be aliased. Is normally a mapped class,
  966. but for convenience can also be a :class:`_expression.FromClause`
  967. element.
  968. :param alias: Optional selectable unit to map the element to. This is
  969. usually used to link the object to a subquery, and should be an aliased
  970. select construct as one would produce from the
  971. :meth:`_query.Query.subquery` method or
  972. the :meth:`_expression.Select.subquery` or
  973. :meth:`_expression.Select.alias` methods of the :func:`_expression.select`
  974. construct.
  975. :param name: optional string name to use for the alias, if not specified
  976. by the ``alias`` parameter. The name, among other things, forms the
  977. attribute name that will be accessible via tuples returned by a
  978. :class:`_query.Query` object. Not supported when creating aliases
  979. of :class:`_sql.Join` objects.
  980. :param flat: Boolean, will be passed through to the
  981. :meth:`_expression.FromClause.alias` call so that aliases of
  982. :class:`_expression.Join` objects will alias the individual tables
  983. inside the join, rather than creating a subquery. This is generally
  984. supported by all modern databases with regards to right-nested joins
  985. and generally produces more efficient queries.
  986. :param adapt_on_names: if True, more liberal "matching" will be used when
  987. mapping the mapped columns of the ORM entity to those of the
  988. given selectable - a name-based match will be performed if the
  989. given selectable doesn't otherwise have a column that corresponds
  990. to one on the entity. The use case for this is when associating
  991. an entity with some derived selectable such as one that uses
  992. aggregate functions::
  993. class UnitPrice(Base):
  994. __tablename__ = 'unit_price'
  995. ...
  996. unit_id = Column(Integer)
  997. price = Column(Numeric)
  998. aggregated_unit_price = Session.query(
  999. func.sum(UnitPrice.price).label('price')
  1000. ).group_by(UnitPrice.unit_id).subquery()
  1001. aggregated_unit_price = aliased(UnitPrice,
  1002. alias=aggregated_unit_price, adapt_on_names=True)
  1003. Above, functions on ``aggregated_unit_price`` which refer to
  1004. ``.price`` will return the
  1005. ``func.sum(UnitPrice.price).label('price')`` column, as it is
  1006. matched on the name "price". Ordinarily, the "price" function
  1007. wouldn't have any "column correspondence" to the actual
  1008. ``UnitPrice.price`` column as it is not a proxy of the original.
  1009. """
  1010. if isinstance(element, expression.FromClause):
  1011. if adapt_on_names:
  1012. raise sa_exc.ArgumentError(
  1013. "adapt_on_names only applies to ORM elements"
  1014. )
  1015. if name:
  1016. return element.alias(name=name, flat=flat)
  1017. else:
  1018. return coercions.expect(
  1019. roles.AnonymizedFromClauseRole, element, flat=flat
  1020. )
  1021. else:
  1022. return AliasedClass(
  1023. element,
  1024. alias=alias,
  1025. flat=flat,
  1026. name=name,
  1027. adapt_on_names=adapt_on_names,
  1028. )
  1029. def with_polymorphic(
  1030. base,
  1031. classes,
  1032. selectable=False,
  1033. flat=False,
  1034. polymorphic_on=None,
  1035. aliased=False,
  1036. innerjoin=False,
  1037. _use_mapper_path=False,
  1038. _existing_alias=None,
  1039. ):
  1040. """Produce an :class:`.AliasedClass` construct which specifies
  1041. columns for descendant mappers of the given base.
  1042. Using this method will ensure that each descendant mapper's
  1043. tables are included in the FROM clause, and will allow filter()
  1044. criterion to be used against those tables. The resulting
  1045. instances will also have those columns already loaded so that
  1046. no "post fetch" of those columns will be required.
  1047. .. seealso::
  1048. :ref:`with_polymorphic` - full discussion of
  1049. :func:`_orm.with_polymorphic`.
  1050. :param base: Base class to be aliased.
  1051. :param classes: a single class or mapper, or list of
  1052. class/mappers, which inherit from the base class.
  1053. Alternatively, it may also be the string ``'*'``, in which case
  1054. all descending mapped classes will be added to the FROM clause.
  1055. :param aliased: when True, the selectable will be aliased. For a
  1056. JOIN, this means the JOIN will be SELECTed from inside of a subquery
  1057. unless the :paramref:`_orm.with_polymorphic.flat` flag is set to
  1058. True, which is recommended for simpler use cases.
  1059. :param flat: Boolean, will be passed through to the
  1060. :meth:`_expression.FromClause.alias` call so that aliases of
  1061. :class:`_expression.Join` objects will alias the individual tables
  1062. inside the join, rather than creating a subquery. This is generally
  1063. supported by all modern databases with regards to right-nested joins
  1064. and generally produces more efficient queries. Setting this flag is
  1065. recommended as long as the resulting SQL is functional.
  1066. :param selectable: a table or subquery that will
  1067. be used in place of the generated FROM clause. This argument is
  1068. required if any of the desired classes use concrete table
  1069. inheritance, since SQLAlchemy currently cannot generate UNIONs
  1070. among tables automatically. If used, the ``selectable`` argument
  1071. must represent the full set of tables and columns mapped by every
  1072. mapped class. Otherwise, the unaccounted mapped columns will
  1073. result in their table being appended directly to the FROM clause
  1074. which will usually lead to incorrect results.
  1075. :param polymorphic_on: a column to be used as the "discriminator"
  1076. column for the given selectable. If not given, the polymorphic_on
  1077. attribute of the base classes' mapper will be used, if any. This
  1078. is useful for mappings that don't have polymorphic loading
  1079. behavior by default.
  1080. :param innerjoin: if True, an INNER JOIN will be used. This should
  1081. only be specified if querying for one specific subtype only
  1082. """
  1083. primary_mapper = _class_to_mapper(base)
  1084. if selectable not in (None, False) and flat:
  1085. raise sa_exc.ArgumentError(
  1086. "the 'flat' and 'selectable' arguments cannot be passed "
  1087. "simultaneously to with_polymorphic()"
  1088. )
  1089. if _existing_alias:
  1090. assert _existing_alias.mapper is primary_mapper
  1091. classes = util.to_set(classes)
  1092. new_classes = set(
  1093. [mp.class_ for mp in _existing_alias.with_polymorphic_mappers]
  1094. )
  1095. if classes == new_classes:
  1096. return _existing_alias
  1097. else:
  1098. classes = classes.union(new_classes)
  1099. mappers, selectable = primary_mapper._with_polymorphic_args(
  1100. classes, selectable, innerjoin=innerjoin
  1101. )
  1102. if aliased or flat:
  1103. selectable = selectable._anonymous_fromclause(flat=flat)
  1104. return AliasedClass(
  1105. base,
  1106. selectable,
  1107. with_polymorphic_mappers=mappers,
  1108. with_polymorphic_discriminator=polymorphic_on,
  1109. use_mapper_path=_use_mapper_path,
  1110. represents_outer_join=not innerjoin,
  1111. )
  1112. @inspection._self_inspects
  1113. class Bundle(ORMColumnsClauseRole, SupportsCloneAnnotations, InspectionAttr):
  1114. """A grouping of SQL expressions that are returned by a :class:`.Query`
  1115. under one namespace.
  1116. The :class:`.Bundle` essentially allows nesting of the tuple-based
  1117. results returned by a column-oriented :class:`_query.Query` object.
  1118. It also
  1119. is extensible via simple subclassing, where the primary capability
  1120. to override is that of how the set of expressions should be returned,
  1121. allowing post-processing as well as custom return types, without
  1122. involving ORM identity-mapped classes.
  1123. .. versionadded:: 0.9.0
  1124. .. seealso::
  1125. :ref:`bundles`
  1126. """
  1127. single_entity = False
  1128. """If True, queries for a single Bundle will be returned as a single
  1129. entity, rather than an element within a keyed tuple."""
  1130. is_clause_element = False
  1131. is_mapper = False
  1132. is_aliased_class = False
  1133. is_bundle = True
  1134. _propagate_attrs = util.immutabledict()
  1135. def __init__(self, name, *exprs, **kw):
  1136. r"""Construct a new :class:`.Bundle`.
  1137. e.g.::
  1138. bn = Bundle("mybundle", MyClass.x, MyClass.y)
  1139. for row in session.query(bn).filter(
  1140. bn.c.x == 5).filter(bn.c.y == 4):
  1141. print(row.mybundle.x, row.mybundle.y)
  1142. :param name: name of the bundle.
  1143. :param \*exprs: columns or SQL expressions comprising the bundle.
  1144. :param single_entity=False: if True, rows for this :class:`.Bundle`
  1145. can be returned as a "single entity" outside of any enclosing tuple
  1146. in the same manner as a mapped entity.
  1147. """
  1148. self.name = self._label = name
  1149. self.exprs = exprs = [
  1150. coercions.expect(
  1151. roles.ColumnsClauseRole, expr, apply_propagate_attrs=self
  1152. )
  1153. for expr in exprs
  1154. ]
  1155. self.c = self.columns = ColumnCollection(
  1156. (getattr(col, "key", col._label), col)
  1157. for col in [e._annotations.get("bundle", e) for e in exprs]
  1158. )
  1159. self.single_entity = kw.pop("single_entity", self.single_entity)
  1160. @property
  1161. def mapper(self):
  1162. return self.exprs[0]._annotations.get("parentmapper", None)
  1163. @property
  1164. def entity(self):
  1165. return self.exprs[0]._annotations.get("parententity", None)
  1166. @property
  1167. def entity_namespace(self):
  1168. return self.c
  1169. columns = None
  1170. """A namespace of SQL expressions referred to by this :class:`.Bundle`.
  1171. e.g.::
  1172. bn = Bundle("mybundle", MyClass.x, MyClass.y)
  1173. q = sess.query(bn).filter(bn.c.x == 5)
  1174. Nesting of bundles is also supported::
  1175. b1 = Bundle("b1",
  1176. Bundle('b2', MyClass.a, MyClass.b),
  1177. Bundle('b3', MyClass.x, MyClass.y)
  1178. )
  1179. q = sess.query(b1).filter(
  1180. b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)
  1181. .. seealso::
  1182. :attr:`.Bundle.c`
  1183. """
  1184. c = None
  1185. """An alias for :attr:`.Bundle.columns`."""
  1186. def _clone(self):
  1187. cloned = self.__class__.__new__(self.__class__)
  1188. cloned.__dict__.update(self.__dict__)
  1189. return cloned
  1190. def __clause_element__(self):
  1191. # ensure existing entity_namespace remains
  1192. annotations = {"bundle": self, "entity_namespace": self}
  1193. annotations.update(self._annotations)
  1194. plugin_subject = self.exprs[0]._propagate_attrs.get(
  1195. "plugin_subject", self.entity
  1196. )
  1197. return (
  1198. expression.ClauseList(
  1199. _literal_as_text_role=roles.ColumnsClauseRole,
  1200. group=False,
  1201. *[e._annotations.get("bundle", e) for e in self.exprs]
  1202. )
  1203. ._annotate(annotations)
  1204. ._set_propagate_attrs(
  1205. # the Bundle *must* use the orm plugin no matter what. the
  1206. # subject can be None but it's much better if it's not.
  1207. {
  1208. "compile_state_plugin": "orm",
  1209. "plugin_subject": plugin_subject,
  1210. }
  1211. )
  1212. )
  1213. @property
  1214. def clauses(self):
  1215. return self.__clause_element__().clauses
  1216. def label(self, name):
  1217. """Provide a copy of this :class:`.Bundle` passing a new label."""
  1218. cloned = self._clone()
  1219. cloned.name = name
  1220. return cloned
  1221. def create_row_processor(self, query, procs, labels):
  1222. """Produce the "row processing" function for this :class:`.Bundle`.
  1223. May be overridden by subclasses.
  1224. .. seealso::
  1225. :ref:`bundles` - includes an example of subclassing.
  1226. """
  1227. keyed_tuple = result_tuple(labels, [() for l in labels])
  1228. def proc(row):
  1229. return keyed_tuple([proc(row) for proc in procs])
  1230. return proc
  1231. def _orm_annotate(element, exclude=None):
  1232. """Deep copy the given ClauseElement, annotating each element with the
  1233. "_orm_adapt" flag.
  1234. Elements within the exclude collection will be cloned but not annotated.
  1235. """
  1236. return sql_util._deep_annotate(element, {"_orm_adapt": True}, exclude)
  1237. def _orm_deannotate(element):
  1238. """Remove annotations that link a column to a particular mapping.
  1239. Note this doesn't affect "remote" and "foreign" annotations
  1240. passed by the :func:`_orm.foreign` and :func:`_orm.remote`
  1241. annotators.
  1242. """
  1243. return sql_util._deep_deannotate(
  1244. element, values=("_orm_adapt", "parententity")
  1245. )
  1246. def _orm_full_deannotate(element):
  1247. return sql_util._deep_deannotate(element)
  1248. class _ORMJoin(expression.Join):
  1249. """Extend Join to support ORM constructs as input."""
  1250. __visit_name__ = expression.Join.__visit_name__
  1251. inherit_cache = True
  1252. def __init__(
  1253. self,
  1254. left,
  1255. right,
  1256. onclause=None,
  1257. isouter=False,
  1258. full=False,
  1259. _left_memo=None,
  1260. _right_memo=None,
  1261. _extra_criteria=(),
  1262. ):
  1263. left_info = inspection.inspect(left)
  1264. right_info = inspection.inspect(right)
  1265. adapt_to = right_info.selectable
  1266. # used by joined eager loader
  1267. self._left_memo = _left_memo
  1268. self._right_memo = _right_memo
  1269. # legacy, for string attr name ON clause. if that's removed
  1270. # then the "_joined_from_info" concept can go
  1271. left_orm_info = getattr(left, "_joined_from_info", left_info)
  1272. self._joined_from_info = right_info
  1273. if isinstance(onclause, util.string_types):
  1274. onclause = getattr(left_orm_info.entity, onclause)
  1275. # ####
  1276. if isinstance(onclause, attributes.QueryableAttribute):
  1277. on_selectable = onclause.comparator._source_selectable()
  1278. prop = onclause.property
  1279. _extra_criteria += onclause._extra_criteria
  1280. elif isinstance(onclause, MapperProperty):
  1281. # used internally by joined eager loader...possibly not ideal
  1282. prop = onclause
  1283. on_selectable = prop.parent.selectable
  1284. else:
  1285. prop = None
  1286. if prop:
  1287. left_selectable = left_info.selectable
  1288. if sql_util.clause_is_present(on_selectable, left_selectable):
  1289. adapt_from = on_selectable
  1290. else:
  1291. adapt_from = left_selectable
  1292. (
  1293. pj,
  1294. sj,
  1295. source,
  1296. dest,
  1297. secondary,
  1298. target_adapter,
  1299. ) = prop._create_joins(
  1300. source_selectable=adapt_from,
  1301. dest_selectable=adapt_to,
  1302. source_polymorphic=True,
  1303. of_type_entity=right_info,
  1304. alias_secondary=True,
  1305. extra_criteria=_extra_criteria,
  1306. )
  1307. if sj is not None:
  1308. if isouter:
  1309. # note this is an inner join from secondary->right
  1310. right = sql.join(secondary, right, sj)
  1311. onclause = pj
  1312. else:
  1313. left = sql.join(left, secondary, pj, isouter)
  1314. onclause = sj
  1315. else:
  1316. onclause = pj
  1317. self._target_adapter = target_adapter
  1318. expression.Join.__init__(self, left, right, onclause, isouter, full)
  1319. if (
  1320. not prop
  1321. and getattr(right_info, "mapper", None)
  1322. and right_info.mapper.single
  1323. ):
  1324. # if single inheritance target and we are using a manual
  1325. # or implicit ON clause, augment it the same way we'd augment the
  1326. # WHERE.
  1327. single_crit = right_info.mapper._single_table_criterion
  1328. if single_crit is not None:
  1329. if right_info.is_aliased_class:
  1330. single_crit = right_info._adapter.traverse(single_crit)
  1331. self.onclause = self.onclause & single_crit
  1332. def _splice_into_center(self, other):
  1333. """Splice a join into the center.
  1334. Given join(a, b) and join(b, c), return join(a, b).join(c)
  1335. """
  1336. leftmost = other
  1337. while isinstance(leftmost, sql.Join):
  1338. leftmost = leftmost.left
  1339. assert self.right is leftmost
  1340. left = _ORMJoin(
  1341. self.left,
  1342. other.left,
  1343. self.onclause,
  1344. isouter=self.isouter,
  1345. _left_memo=self._left_memo,
  1346. _right_memo=other._left_memo,
  1347. )
  1348. return _ORMJoin(
  1349. left,
  1350. other.right,
  1351. other.onclause,
  1352. isouter=other.isouter,
  1353. _right_memo=other._right_memo,
  1354. )
  1355. def join(
  1356. self,
  1357. right,
  1358. onclause=None,
  1359. isouter=False,
  1360. full=False,
  1361. join_to_left=None,
  1362. ):
  1363. return _ORMJoin(self, right, onclause, full=full, isouter=isouter)
  1364. def outerjoin(self, right, onclause=None, full=False, join_to_left=None):
  1365. return _ORMJoin(self, right, onclause, isouter=True, full=full)
  1366. def join(
  1367. left, right, onclause=None, isouter=False, full=False, join_to_left=None
  1368. ):
  1369. r"""Produce an inner join between left and right clauses.
  1370. :func:`_orm.join` is an extension to the core join interface
  1371. provided by :func:`_expression.join()`, where the
  1372. left and right selectables may be not only core selectable
  1373. objects such as :class:`_schema.Table`, but also mapped classes or
  1374. :class:`.AliasedClass` instances. The "on" clause can
  1375. be a SQL expression, or an attribute or string name
  1376. referencing a configured :func:`_orm.relationship`.
  1377. :func:`_orm.join` is not commonly needed in modern usage,
  1378. as its functionality is encapsulated within that of the
  1379. :meth:`_query.Query.join` method, which features a
  1380. significant amount of automation beyond :func:`_orm.join`
  1381. by itself. Explicit usage of :func:`_orm.join`
  1382. with :class:`_query.Query` involves usage of the
  1383. :meth:`_query.Query.select_from` method, as in::
  1384. from sqlalchemy.orm import join
  1385. session.query(User).\
  1386. select_from(join(User, Address, User.addresses)).\
  1387. filter(Address.email_address=='foo@bar.com')
  1388. In modern SQLAlchemy the above join can be written more
  1389. succinctly as::
  1390. session.query(User).\
  1391. join(User.addresses).\
  1392. filter(Address.email_address=='foo@bar.com')
  1393. See :meth:`_query.Query.join` for information on modern usage
  1394. of ORM level joins.
  1395. .. deprecated:: 0.8
  1396. the ``join_to_left`` parameter is deprecated, and will be removed
  1397. in a future release. The parameter has no effect.
  1398. """
  1399. return _ORMJoin(left, right, onclause, isouter, full)
  1400. def outerjoin(left, right, onclause=None, full=False, join_to_left=None):
  1401. """Produce a left outer join between left and right clauses.
  1402. This is the "outer join" version of the :func:`_orm.join` function,
  1403. featuring the same behavior except that an OUTER JOIN is generated.
  1404. See that function's documentation for other usage details.
  1405. """
  1406. return _ORMJoin(left, right, onclause, True, full)
  1407. def with_parent(instance, prop, from_entity=None):
  1408. """Create filtering criterion that relates this query's primary entity
  1409. to the given related instance, using established
  1410. :func:`_orm.relationship()`
  1411. configuration.
  1412. E.g.::
  1413. stmt = select(Address).where(with_parent(some_user, Address.user))
  1414. The SQL rendered is the same as that rendered when a lazy loader
  1415. would fire off from the given parent on that attribute, meaning
  1416. that the appropriate state is taken from the parent object in
  1417. Python without the need to render joins to the parent table
  1418. in the rendered statement.
  1419. The given property may also make use of :meth:`_orm.PropComparator.of_type`
  1420. to indicate the left side of the criteria::
  1421. a1 = aliased(Address)
  1422. a2 = aliased(Address)
  1423. stmt = select(a1, a2).where(
  1424. with_parent(u1, User.addresses.of_type(a2))
  1425. )
  1426. The above use is equivalent to using the
  1427. :func:`_orm.with_parent.from_entity` argument::
  1428. a1 = aliased(Address)
  1429. a2 = aliased(Address)
  1430. stmt = select(a1, a2).where(
  1431. with_parent(u1, User.addresses, from_entity=a2)
  1432. )
  1433. :param instance:
  1434. An instance which has some :func:`_orm.relationship`.
  1435. :param property:
  1436. String property name, or class-bound attribute, which indicates
  1437. what relationship from the instance should be used to reconcile the
  1438. parent/child relationship.
  1439. .. deprecated:: 1.4 Using strings is deprecated and will be removed
  1440. in SQLAlchemy 2.0. Please use the class-bound attribute directly.
  1441. :param from_entity:
  1442. Entity in which to consider as the left side. This defaults to the
  1443. "zero" entity of the :class:`_query.Query` itself.
  1444. .. versionadded:: 1.2
  1445. """
  1446. if isinstance(prop, util.string_types):
  1447. util.warn_deprecated_20(
  1448. "Using strings to indicate relationship names in the ORM "
  1449. "with_parent() function is deprecated and will be removed "
  1450. "SQLAlchemy 2.0. Please use the class-bound attribute directly."
  1451. )
  1452. mapper = object_mapper(instance)
  1453. prop = getattr(mapper.class_, prop).property
  1454. elif isinstance(prop, attributes.QueryableAttribute):
  1455. if prop._of_type:
  1456. from_entity = prop._of_type
  1457. prop = prop.property
  1458. return prop._with_parent(instance, from_entity=from_entity)
  1459. def has_identity(object_):
  1460. """Return True if the given object has a database
  1461. identity.
  1462. This typically corresponds to the object being
  1463. in either the persistent or detached state.
  1464. .. seealso::
  1465. :func:`.was_deleted`
  1466. """
  1467. state = attributes.instance_state(object_)
  1468. return state.has_identity
  1469. def was_deleted(object_):
  1470. """Return True if the given object was deleted
  1471. within a session flush.
  1472. This is regardless of whether or not the object is
  1473. persistent or detached.
  1474. .. seealso::
  1475. :attr:`.InstanceState.was_deleted`
  1476. """
  1477. state = attributes.instance_state(object_)
  1478. return state.was_deleted
  1479. def _entity_corresponds_to(given, entity):
  1480. """determine if 'given' corresponds to 'entity', in terms
  1481. of an entity passed to Query that would match the same entity
  1482. being referred to elsewhere in the query.
  1483. """
  1484. if entity.is_aliased_class:
  1485. if given.is_aliased_class:
  1486. if entity._base_alias() is given._base_alias():
  1487. return True
  1488. return False
  1489. elif given.is_aliased_class:
  1490. if given._use_mapper_path:
  1491. return entity in given.with_polymorphic_mappers
  1492. else:
  1493. return entity is given
  1494. return entity.common_parent(given)
  1495. def _entity_corresponds_to_use_path_impl(given, entity):
  1496. """determine if 'given' corresponds to 'entity', in terms
  1497. of a path of loader options where a mapped attribute is taken to
  1498. be a member of a parent entity.
  1499. e.g.::
  1500. someoption(A).someoption(A.b) # -> fn(A, A) -> True
  1501. someoption(A).someoption(C.d) # -> fn(A, C) -> False
  1502. a1 = aliased(A)
  1503. someoption(a1).someoption(A.b) # -> fn(a1, A) -> False
  1504. someoption(a1).someoption(a1.b) # -> fn(a1, a1) -> True
  1505. wp = with_polymorphic(A, [A1, A2])
  1506. someoption(wp).someoption(A1.foo) # -> fn(wp, A1) -> False
  1507. someoption(wp).someoption(wp.A1.foo) # -> fn(wp, wp.A1) -> True
  1508. """
  1509. if given.is_aliased_class:
  1510. return (
  1511. entity.is_aliased_class
  1512. and not entity._use_mapper_path
  1513. and (given is entity or given in entity._with_polymorphic_entities)
  1514. )
  1515. elif not entity.is_aliased_class:
  1516. return given.common_parent(entity.mapper)
  1517. else:
  1518. return (
  1519. entity._use_mapper_path
  1520. and given in entity.with_polymorphic_mappers
  1521. )
  1522. def _entity_isa(given, mapper):
  1523. """determine if 'given' "is a" mapper, in terms of the given
  1524. would load rows of type 'mapper'.
  1525. """
  1526. if given.is_aliased_class:
  1527. return mapper in given.with_polymorphic_mappers or given.mapper.isa(
  1528. mapper
  1529. )
  1530. elif given.with_polymorphic_mappers:
  1531. return mapper in given.with_polymorphic_mappers
  1532. else:
  1533. return given.isa(mapper)
  1534. def randomize_unitofwork():
  1535. """Use random-ordering sets within the unit of work in order
  1536. to detect unit of work sorting issues.
  1537. This is a utility function that can be used to help reproduce
  1538. inconsistent unit of work sorting issues. For example,
  1539. if two kinds of objects A and B are being inserted, and
  1540. B has a foreign key reference to A - the A must be inserted first.
  1541. However, if there is no relationship between A and B, the unit of work
  1542. won't know to perform this sorting, and an operation may or may not
  1543. fail, depending on how the ordering works out. Since Python sets
  1544. and dictionaries have non-deterministic ordering, such an issue may
  1545. occur on some runs and not on others, and in practice it tends to
  1546. have a great dependence on the state of the interpreter. This leads
  1547. to so-called "heisenbugs" where changing entirely irrelevant aspects
  1548. of the test program still cause the failure behavior to change.
  1549. By calling ``randomize_unitofwork()`` when a script first runs, the
  1550. ordering of a key series of sets within the unit of work implementation
  1551. are randomized, so that the script can be minimized down to the
  1552. fundamental mapping and operation that's failing, while still reproducing
  1553. the issue on at least some runs.
  1554. This utility is also available when running the test suite via the
  1555. ``--reversetop`` flag.
  1556. """
  1557. from sqlalchemy.orm import unitofwork, session, mapper, dependency
  1558. from sqlalchemy.util import topological
  1559. from sqlalchemy.testing.util import RandomSet
  1560. topological.set = (
  1561. unitofwork.set
  1562. ) = session.set = mapper.set = dependency.set = RandomSet
  1563. def _getitem(iterable_query, item, allow_negative):
  1564. """calculate __getitem__ in terms of an iterable query object
  1565. that also has a slice() method.
  1566. """
  1567. def _no_negative_indexes():
  1568. if not allow_negative:
  1569. raise IndexError(
  1570. "negative indexes are not accepted by SQL "
  1571. "index / slice operators"
  1572. )
  1573. else:
  1574. util.warn_deprecated_20(
  1575. "Support for negative indexes for SQL index / slice operators "
  1576. "will be "
  1577. "removed in 2.0; these operators fetch the complete result "
  1578. "and do not work efficiently."
  1579. )
  1580. if isinstance(item, slice):
  1581. start, stop, step = util.decode_slice(item)
  1582. if (
  1583. isinstance(stop, int)
  1584. and isinstance(start, int)
  1585. and stop - start <= 0
  1586. ):
  1587. return []
  1588. elif (isinstance(start, int) and start < 0) or (
  1589. isinstance(stop, int) and stop < 0
  1590. ):
  1591. _no_negative_indexes()
  1592. return list(iterable_query)[item]
  1593. res = iterable_query.slice(start, stop)
  1594. if step is not None:
  1595. return list(res)[None : None : item.step]
  1596. else:
  1597. return list(res)
  1598. else:
  1599. if item == -1:
  1600. _no_negative_indexes()
  1601. return list(iterable_query)[-1]
  1602. else:
  1603. return list(iterable_query[item : item + 1])[0]