util.py 74 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101
  1. # orm/util.py
  2. # Copyright (C) 2005-2022 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: https://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. nest_adapters = False
  406. if alias is None:
  407. if insp.is_aliased_class and insp.selectable._is_subquery:
  408. alias = insp.selectable.alias()
  409. else:
  410. alias = (
  411. mapper._with_polymorphic_selectable._anonymous_fromclause(
  412. name=name,
  413. flat=flat,
  414. )
  415. )
  416. elif insp.is_aliased_class:
  417. nest_adapters = True
  418. self._aliased_insp = AliasedInsp(
  419. self,
  420. insp,
  421. alias,
  422. name,
  423. with_polymorphic_mappers
  424. if with_polymorphic_mappers
  425. else mapper.with_polymorphic_mappers,
  426. with_polymorphic_discriminator
  427. if with_polymorphic_discriminator is not None
  428. else mapper.polymorphic_on,
  429. base_alias,
  430. use_mapper_path,
  431. adapt_on_names,
  432. represents_outer_join,
  433. nest_adapters,
  434. )
  435. self.__name__ = "AliasedClass_%s" % mapper.class_.__name__
  436. @classmethod
  437. def _reconstitute_from_aliased_insp(cls, aliased_insp):
  438. obj = cls.__new__(cls)
  439. obj.__name__ = "AliasedClass_%s" % aliased_insp.mapper.class_.__name__
  440. obj._aliased_insp = aliased_insp
  441. if aliased_insp._is_with_polymorphic:
  442. for sub_aliased_insp in aliased_insp._with_polymorphic_entities:
  443. if sub_aliased_insp is not aliased_insp:
  444. ent = AliasedClass._reconstitute_from_aliased_insp(
  445. sub_aliased_insp
  446. )
  447. setattr(obj, sub_aliased_insp.class_.__name__, ent)
  448. return obj
  449. def __getattr__(self, key):
  450. try:
  451. _aliased_insp = self.__dict__["_aliased_insp"]
  452. except KeyError:
  453. raise AttributeError()
  454. else:
  455. target = _aliased_insp._target
  456. # maintain all getattr mechanics
  457. attr = getattr(target, key)
  458. # attribute is a method, that will be invoked against a
  459. # "self"; so just return a new method with the same function and
  460. # new self
  461. if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
  462. return types.MethodType(attr.__func__, self)
  463. # attribute is a descriptor, that will be invoked against a
  464. # "self"; so invoke the descriptor against this self
  465. if hasattr(attr, "__get__"):
  466. attr = attr.__get__(None, self)
  467. # attributes within the QueryableAttribute system will want this
  468. # to be invoked so the object can be adapted
  469. if hasattr(attr, "adapt_to_entity"):
  470. attr = attr.adapt_to_entity(_aliased_insp)
  471. setattr(self, key, attr)
  472. return attr
  473. def _get_from_serialized(self, key, mapped_class, aliased_insp):
  474. # this method is only used in terms of the
  475. # sqlalchemy.ext.serializer extension
  476. attr = getattr(mapped_class, key)
  477. if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
  478. return types.MethodType(attr.__func__, self)
  479. # attribute is a descriptor, that will be invoked against a
  480. # "self"; so invoke the descriptor against this self
  481. if hasattr(attr, "__get__"):
  482. attr = attr.__get__(None, self)
  483. # attributes within the QueryableAttribute system will want this
  484. # to be invoked so the object can be adapted
  485. if hasattr(attr, "adapt_to_entity"):
  486. aliased_insp._weak_entity = weakref.ref(self)
  487. attr = attr.adapt_to_entity(aliased_insp)
  488. setattr(self, key, attr)
  489. return attr
  490. def __repr__(self):
  491. return "<AliasedClass at 0x%x; %s>" % (
  492. id(self),
  493. self._aliased_insp._target.__name__,
  494. )
  495. def __str__(self):
  496. return str(self._aliased_insp)
  497. class AliasedInsp(
  498. ORMEntityColumnsClauseRole,
  499. ORMFromClauseRole,
  500. sql_base.MemoizedHasCacheKey,
  501. InspectionAttr,
  502. ):
  503. """Provide an inspection interface for an
  504. :class:`.AliasedClass` object.
  505. The :class:`.AliasedInsp` object is returned
  506. given an :class:`.AliasedClass` using the
  507. :func:`_sa.inspect` function::
  508. from sqlalchemy import inspect
  509. from sqlalchemy.orm import aliased
  510. my_alias = aliased(MyMappedClass)
  511. insp = inspect(my_alias)
  512. Attributes on :class:`.AliasedInsp`
  513. include:
  514. * ``entity`` - the :class:`.AliasedClass` represented.
  515. * ``mapper`` - the :class:`_orm.Mapper` mapping the underlying class.
  516. * ``selectable`` - the :class:`_expression.Alias`
  517. construct which ultimately
  518. represents an aliased :class:`_schema.Table` or
  519. :class:`_expression.Select`
  520. construct.
  521. * ``name`` - the name of the alias. Also is used as the attribute
  522. name when returned in a result tuple from :class:`_query.Query`.
  523. * ``with_polymorphic_mappers`` - collection of :class:`_orm.Mapper`
  524. objects
  525. indicating all those mappers expressed in the select construct
  526. for the :class:`.AliasedClass`.
  527. * ``polymorphic_on`` - an alternate column or SQL expression which
  528. will be used as the "discriminator" for a polymorphic load.
  529. .. seealso::
  530. :ref:`inspection_toplevel`
  531. """
  532. def __init__(
  533. self,
  534. entity,
  535. inspected,
  536. selectable,
  537. name,
  538. with_polymorphic_mappers,
  539. polymorphic_on,
  540. _base_alias,
  541. _use_mapper_path,
  542. adapt_on_names,
  543. represents_outer_join,
  544. nest_adapters,
  545. ):
  546. mapped_class_or_ac = inspected.entity
  547. mapper = inspected.mapper
  548. self._weak_entity = weakref.ref(entity)
  549. self.mapper = mapper
  550. self.selectable = (
  551. self.persist_selectable
  552. ) = self.local_table = selectable
  553. self.name = name
  554. self.polymorphic_on = polymorphic_on
  555. self._base_alias = weakref.ref(_base_alias or self)
  556. self._use_mapper_path = _use_mapper_path
  557. self.represents_outer_join = represents_outer_join
  558. self._nest_adapters = nest_adapters
  559. if with_polymorphic_mappers:
  560. self._is_with_polymorphic = True
  561. self.with_polymorphic_mappers = with_polymorphic_mappers
  562. self._with_polymorphic_entities = []
  563. for poly in self.with_polymorphic_mappers:
  564. if poly is not mapper:
  565. ent = AliasedClass(
  566. poly.class_,
  567. selectable,
  568. base_alias=self,
  569. adapt_on_names=adapt_on_names,
  570. use_mapper_path=_use_mapper_path,
  571. )
  572. setattr(self.entity, poly.class_.__name__, ent)
  573. self._with_polymorphic_entities.append(ent._aliased_insp)
  574. else:
  575. self._is_with_polymorphic = False
  576. self.with_polymorphic_mappers = [mapper]
  577. self._adapter = sql_util.ColumnAdapter(
  578. selectable,
  579. equivalents=mapper._equivalent_columns,
  580. adapt_on_names=adapt_on_names,
  581. anonymize_labels=True,
  582. # make sure the adapter doesn't try to grab other tables that
  583. # are not even the thing we are mapping, such as embedded
  584. # selectables in subqueries or CTEs. See issue #6060
  585. adapt_from_selectables=[
  586. m.selectable for m in self.with_polymorphic_mappers
  587. ],
  588. )
  589. if nest_adapters:
  590. self._adapter = inspected._adapter.wrap(self._adapter)
  591. self._adapt_on_names = adapt_on_names
  592. self._target = mapped_class_or_ac
  593. # self._target = mapper.class_ # mapped_class_or_ac
  594. @property
  595. def entity(self):
  596. # to eliminate reference cycles, the AliasedClass is held weakly.
  597. # this produces some situations where the AliasedClass gets lost,
  598. # particularly when one is created internally and only the AliasedInsp
  599. # is passed around.
  600. # to work around this case, we just generate a new one when we need
  601. # it, as it is a simple class with very little initial state on it.
  602. ent = self._weak_entity()
  603. if ent is None:
  604. ent = AliasedClass._reconstitute_from_aliased_insp(self)
  605. self._weak_entity = weakref.ref(ent)
  606. return ent
  607. is_aliased_class = True
  608. "always returns True"
  609. @util.memoized_instancemethod
  610. def __clause_element__(self):
  611. return self.selectable._annotate(
  612. {
  613. "parentmapper": self.mapper,
  614. "parententity": self,
  615. "entity_namespace": self,
  616. }
  617. )._set_propagate_attrs(
  618. {"compile_state_plugin": "orm", "plugin_subject": self}
  619. )
  620. @property
  621. def entity_namespace(self):
  622. return self.entity
  623. _cache_key_traversal = [
  624. ("name", visitors.ExtendedInternalTraversal.dp_string),
  625. ("_adapt_on_names", visitors.ExtendedInternalTraversal.dp_boolean),
  626. ("selectable", visitors.ExtendedInternalTraversal.dp_clauseelement),
  627. ]
  628. @property
  629. def class_(self):
  630. """Return the mapped class ultimately represented by this
  631. :class:`.AliasedInsp`."""
  632. return self.mapper.class_
  633. @property
  634. def _path_registry(self):
  635. if self._use_mapper_path:
  636. return self.mapper._path_registry
  637. else:
  638. return PathRegistry.per_mapper(self)
  639. def __getstate__(self):
  640. return {
  641. "entity": self.entity,
  642. "mapper": self.mapper,
  643. "alias": self.selectable,
  644. "name": self.name,
  645. "adapt_on_names": self._adapt_on_names,
  646. "with_polymorphic_mappers": self.with_polymorphic_mappers,
  647. "with_polymorphic_discriminator": self.polymorphic_on,
  648. "base_alias": self._base_alias(),
  649. "use_mapper_path": self._use_mapper_path,
  650. "represents_outer_join": self.represents_outer_join,
  651. "nest_adapters": self._nest_adapters,
  652. }
  653. def __setstate__(self, state):
  654. self.__init__(
  655. state["entity"],
  656. state["mapper"],
  657. state["alias"],
  658. state["name"],
  659. state["with_polymorphic_mappers"],
  660. state["with_polymorphic_discriminator"],
  661. state["base_alias"],
  662. state["use_mapper_path"],
  663. state["adapt_on_names"],
  664. state["represents_outer_join"],
  665. state["nest_adapters"],
  666. )
  667. def _adapt_element(self, elem, key=None):
  668. d = {
  669. "parententity": self,
  670. "parentmapper": self.mapper,
  671. }
  672. if key:
  673. d["proxy_key"] = key
  674. return (
  675. self._adapter.traverse(elem)
  676. ._annotate(d)
  677. ._set_propagate_attrs(
  678. {"compile_state_plugin": "orm", "plugin_subject": self}
  679. )
  680. )
  681. def _entity_for_mapper(self, mapper):
  682. self_poly = self.with_polymorphic_mappers
  683. if mapper in self_poly:
  684. if mapper is self.mapper:
  685. return self
  686. else:
  687. return getattr(
  688. self.entity, mapper.class_.__name__
  689. )._aliased_insp
  690. elif mapper.isa(self.mapper):
  691. return self
  692. else:
  693. assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
  694. @util.memoized_property
  695. def _get_clause(self):
  696. onclause, replacemap = self.mapper._get_clause
  697. return (
  698. self._adapter.traverse(onclause),
  699. {
  700. self._adapter.traverse(col): param
  701. for col, param in replacemap.items()
  702. },
  703. )
  704. @util.memoized_property
  705. def _memoized_values(self):
  706. return {}
  707. @util.memoized_property
  708. def _all_column_expressions(self):
  709. if self._is_with_polymorphic:
  710. cols_plus_keys = self.mapper._columns_plus_keys(
  711. [ent.mapper for ent in self._with_polymorphic_entities]
  712. )
  713. else:
  714. cols_plus_keys = self.mapper._columns_plus_keys()
  715. cols_plus_keys = [
  716. (key, self._adapt_element(col)) for key, col in cols_plus_keys
  717. ]
  718. return ColumnCollection(cols_plus_keys)
  719. def _memo(self, key, callable_, *args, **kw):
  720. if key in self._memoized_values:
  721. return self._memoized_values[key]
  722. else:
  723. self._memoized_values[key] = value = callable_(*args, **kw)
  724. return value
  725. def __repr__(self):
  726. if self.with_polymorphic_mappers:
  727. with_poly = "(%s)" % ", ".join(
  728. mp.class_.__name__ for mp in self.with_polymorphic_mappers
  729. )
  730. else:
  731. with_poly = ""
  732. return "<AliasedInsp at 0x%x; %s%s>" % (
  733. id(self),
  734. self.class_.__name__,
  735. with_poly,
  736. )
  737. def __str__(self):
  738. if self._is_with_polymorphic:
  739. return "with_polymorphic(%s, [%s])" % (
  740. self._target.__name__,
  741. ", ".join(
  742. mp.class_.__name__
  743. for mp in self.with_polymorphic_mappers
  744. if mp is not self.mapper
  745. ),
  746. )
  747. else:
  748. return "aliased(%s)" % (self._target.__name__,)
  749. class _WrapUserEntity(object):
  750. """A wrapper used within the loader_criteria lambda caller so that
  751. we can bypass declared_attr descriptors on unmapped mixins, which
  752. normally emit a warning for such use.
  753. might also be useful for other per-lambda instrumentations should
  754. the need arise.
  755. """
  756. def __init__(self, subject):
  757. self.subject = subject
  758. @util.preload_module("sqlalchemy.orm.decl_api")
  759. def __getattribute__(self, name):
  760. decl_api = util.preloaded.orm.decl_api
  761. subject = object.__getattribute__(self, "subject")
  762. if name in subject.__dict__ and isinstance(
  763. subject.__dict__[name], decl_api.declared_attr
  764. ):
  765. return subject.__dict__[name].fget(subject)
  766. else:
  767. return getattr(subject, name)
  768. class LoaderCriteriaOption(CriteriaOption):
  769. """Add additional WHERE criteria to the load for all occurrences of
  770. a particular entity.
  771. :class:`_orm.LoaderCriteriaOption` is invoked using the
  772. :func:`_orm.with_loader_criteria` function; see that function for
  773. details.
  774. .. versionadded:: 1.4
  775. """
  776. _traverse_internals = [
  777. ("root_entity", visitors.ExtendedInternalTraversal.dp_plain_obj),
  778. ("entity", visitors.ExtendedInternalTraversal.dp_has_cache_key),
  779. ("where_criteria", visitors.InternalTraversal.dp_clauseelement),
  780. ("include_aliases", visitors.InternalTraversal.dp_boolean),
  781. ("propagate_to_loaders", visitors.InternalTraversal.dp_boolean),
  782. ]
  783. def __init__(
  784. self,
  785. entity_or_base,
  786. where_criteria,
  787. loader_only=False,
  788. include_aliases=False,
  789. propagate_to_loaders=True,
  790. track_closure_variables=True,
  791. ):
  792. """Add additional WHERE criteria to the load for all occurrences of
  793. a particular entity.
  794. .. versionadded:: 1.4
  795. The :func:`_orm.with_loader_criteria` option is intended to add
  796. limiting criteria to a particular kind of entity in a query,
  797. **globally**, meaning it will apply to the entity as it appears
  798. in the SELECT query as well as within any subqueries, join
  799. conditions, and relationship loads, including both eager and lazy
  800. loaders, without the need for it to be specified in any particular
  801. part of the query. The rendering logic uses the same system used by
  802. single table inheritance to ensure a certain discriminator is applied
  803. to a table.
  804. E.g., using :term:`2.0-style` queries, we can limit the way the
  805. ``User.addresses`` collection is loaded, regardless of the kind
  806. of loading used::
  807. from sqlalchemy.orm import with_loader_criteria
  808. stmt = select(User).options(
  809. selectinload(User.addresses),
  810. with_loader_criteria(Address, Address.email_address != 'foo'))
  811. )
  812. Above, the "selectinload" for ``User.addresses`` will apply the
  813. given filtering criteria to the WHERE clause.
  814. Another example, where the filtering will be applied to the
  815. ON clause of the join, in this example using :term:`1.x style`
  816. queries::
  817. q = session.query(User).outerjoin(User.addresses).options(
  818. with_loader_criteria(Address, Address.email_address != 'foo'))
  819. )
  820. The primary purpose of :func:`_orm.with_loader_criteria` is to use
  821. it in the :meth:`_orm.SessionEvents.do_orm_execute` event handler
  822. to ensure that all occurrences of a particular entity are filtered
  823. in a certain way, such as filtering for access control roles. It
  824. also can be used to apply criteria to relationship loads. In the
  825. example below, we can apply a certain set of rules to all queries
  826. emitted by a particular :class:`_orm.Session`::
  827. session = Session(bind=engine)
  828. @event.listens_for("do_orm_execute", session)
  829. def _add_filtering_criteria(execute_state):
  830. if (
  831. execute_state.is_select
  832. and not execute_state.is_column_load
  833. and not execute_state.is_relationship_load
  834. ):
  835. execute_state.statement = execute_state.statement.options(
  836. with_loader_criteria(
  837. SecurityRole,
  838. lambda cls: cls.role.in_(['some_role']),
  839. include_aliases=True
  840. )
  841. )
  842. In the above example, the :meth:`_orm.SessionEvents.do_orm_execute`
  843. event will intercept all queries emitted using the
  844. :class:`_orm.Session`. For those queries which are SELECT statements
  845. and are not attribute or relationship loads a custom
  846. :func:`_orm.with_loader_criteria` option is added to the query. The
  847. :func:`_orm.with_loader_criteria` option will be used in the given
  848. statement and will also be automatically propagated to all relationship
  849. loads that descend from this query.
  850. The criteria argument given is a ``lambda`` that accepts a ``cls``
  851. argument. The given class will expand to include all mapped subclass
  852. and need not itself be a mapped class.
  853. .. tip::
  854. When using :func:`_orm.with_loader_criteria` option in
  855. conjunction with the :func:`_orm.contains_eager` loader option,
  856. it's important to note that :func:`_orm.with_loader_criteria` only
  857. affects the part of the query that determines what SQL is rendered
  858. in terms of the WHERE and FROM clauses. The
  859. :func:`_orm.contains_eager` option does not affect the rendering of
  860. the SELECT statement outside of the columns clause, so does not have
  861. any interaction with the :func:`_orm.with_loader_criteria` option.
  862. However, the way things "work" is that :func:`_orm.contains_eager`
  863. is meant to be used with a query that is already selecting from the
  864. additional entities in some way, where
  865. :func:`_orm.with_loader_criteria` can apply it's additional
  866. criteria.
  867. In the example below, assuming a mapping relationship as
  868. ``A -> A.bs -> B``, the given :func:`_orm.with_loader_criteria`
  869. option will affect the way in which the JOIN is rendered::
  870. stmt = select(A).join(A.bs).options(
  871. contains_eager(A.bs),
  872. with_loader_criteria(B, B.flag == 1)
  873. )
  874. Above, the given :func:`_orm.with_loader_criteria` option will
  875. affect the ON clause of the JOIN that is specified by
  876. ``.join(A.bs)``, so is applied as expected. The
  877. :func:`_orm.contains_eager` option has the effect that columns from
  878. ``B`` are added to the columns clause::
  879. SELECT
  880. b.id, b.a_id, b.data, b.flag,
  881. a.id AS id_1,
  882. a.data AS data_1
  883. FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1
  884. The use of the :func:`_orm.contains_eager` option within the above
  885. statement has no effect on the behavior of the
  886. :func:`_orm.with_loader_criteria` option. If the
  887. :func:`_orm.contains_eager` option were omitted, the SQL would be
  888. the same as regards the FROM and WHERE clauses, where
  889. :func:`_orm.with_loader_criteria` continues to add its criteria to
  890. the ON clause of the JOIN. The addition of
  891. :func:`_orm.contains_eager` only affects the columns clause, in that
  892. additional columns against ``b`` are added which are then consumed
  893. by the ORM to produce ``B`` instances.
  894. .. warning:: The use of a lambda inside of the call to
  895. :func:`_orm.with_loader_criteria` is only invoked **once per unique
  896. class**. Custom functions should not be invoked within this lambda.
  897. See :ref:`engine_lambda_caching` for an overview of the "lambda SQL"
  898. feature, which is for advanced use only.
  899. :param entity_or_base: a mapped class, or a class that is a super
  900. class of a particular set of mapped classes, to which the rule
  901. will apply.
  902. :param where_criteria: a Core SQL expression that applies limiting
  903. criteria. This may also be a "lambda:" or Python function that
  904. accepts a target class as an argument, when the given class is
  905. a base with many different mapped subclasses.
  906. :param include_aliases: if True, apply the rule to :func:`_orm.aliased`
  907. constructs as well.
  908. :param propagate_to_loaders: defaults to True, apply to relationship
  909. loaders such as lazy loaders.
  910. .. seealso::
  911. :ref:`examples_session_orm_events` - includes examples of using
  912. :func:`_orm.with_loader_criteria`.
  913. :ref:`do_orm_execute_global_criteria` - basic example on how to
  914. combine :func:`_orm.with_loader_criteria` with the
  915. :meth:`_orm.SessionEvents.do_orm_execute` event.
  916. :param track_closure_variables: when False, closure variables inside
  917. of a lambda expression will not be used as part of
  918. any cache key. This allows more complex expressions to be used
  919. inside of a lambda expression but requires that the lambda ensures
  920. it returns the identical SQL every time given a particular class.
  921. .. versionadded:: 1.4.0b2
  922. """
  923. entity = inspection.inspect(entity_or_base, False)
  924. if entity is None:
  925. self.root_entity = entity_or_base
  926. self.entity = None
  927. else:
  928. self.root_entity = None
  929. self.entity = entity
  930. if callable(where_criteria):
  931. self.deferred_where_criteria = True
  932. self.where_criteria = lambdas.DeferredLambdaElement(
  933. where_criteria,
  934. roles.WhereHavingRole,
  935. lambda_args=(
  936. _WrapUserEntity(
  937. self.root_entity
  938. if self.root_entity is not None
  939. else self.entity.entity,
  940. ),
  941. ),
  942. opts=lambdas.LambdaOptions(
  943. track_closure_variables=track_closure_variables
  944. ),
  945. )
  946. else:
  947. self.deferred_where_criteria = False
  948. self.where_criteria = coercions.expect(
  949. roles.WhereHavingRole, where_criteria
  950. )
  951. self.include_aliases = include_aliases
  952. self.propagate_to_loaders = propagate_to_loaders
  953. def _all_mappers(self):
  954. if self.entity:
  955. for ent in self.entity.mapper.self_and_descendants:
  956. yield ent
  957. else:
  958. stack = list(self.root_entity.__subclasses__())
  959. while stack:
  960. subclass = stack.pop(0)
  961. ent = inspection.inspect(subclass, raiseerr=False)
  962. if ent:
  963. for mp in ent.mapper.self_and_descendants:
  964. yield mp
  965. else:
  966. stack.extend(subclass.__subclasses__())
  967. def _should_include(self, compile_state):
  968. if (
  969. compile_state.select_statement._annotations.get(
  970. "for_loader_criteria", None
  971. )
  972. is self
  973. ):
  974. return False
  975. return True
  976. def _resolve_where_criteria(self, ext_info):
  977. if self.deferred_where_criteria:
  978. crit = self.where_criteria._resolve_with_args(ext_info.entity)
  979. else:
  980. crit = self.where_criteria
  981. return sql_util._deep_annotate(
  982. crit, {"for_loader_criteria": self}, detect_subquery_cols=True
  983. )
  984. def process_compile_state_replaced_entities(
  985. self, compile_state, mapper_entities
  986. ):
  987. return self.process_compile_state(compile_state)
  988. def process_compile_state(self, compile_state):
  989. """Apply a modification to a given :class:`.CompileState`."""
  990. # if options to limit the criteria to immediate query only,
  991. # use compile_state.attributes instead
  992. if compile_state.compile_options._with_polymorphic_adapt_map:
  993. util.warn(
  994. "The with_loader_criteria() function may not work "
  995. "correctly with the legacy Query.with_polymorphic() feature. "
  996. "Please migrate code to use the with_polymorphic() standalone "
  997. "function before using with_loader_criteria()."
  998. )
  999. if not compile_state.compile_options._for_refresh_state:
  1000. self.get_global_criteria(compile_state.global_attributes)
  1001. def get_global_criteria(self, attributes):
  1002. for mp in self._all_mappers():
  1003. load_criteria = attributes.setdefault(
  1004. ("additional_entity_criteria", mp), []
  1005. )
  1006. load_criteria.append(self)
  1007. inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
  1008. inspection._inspects(AliasedInsp)(lambda target: target)
  1009. def aliased(element, alias=None, name=None, flat=False, adapt_on_names=False):
  1010. """Produce an alias of the given element, usually an :class:`.AliasedClass`
  1011. instance.
  1012. E.g.::
  1013. my_alias = aliased(MyClass)
  1014. session.query(MyClass, my_alias).filter(MyClass.id > my_alias.id)
  1015. The :func:`.aliased` function is used to create an ad-hoc mapping of a
  1016. mapped class to a new selectable. By default, a selectable is generated
  1017. from the normally mapped selectable (typically a :class:`_schema.Table`
  1018. ) using the
  1019. :meth:`_expression.FromClause.alias` method. However, :func:`.aliased`
  1020. can also be
  1021. used to link the class to a new :func:`_expression.select` statement.
  1022. Also, the :func:`.with_polymorphic` function is a variant of
  1023. :func:`.aliased` that is intended to specify a so-called "polymorphic
  1024. selectable", that corresponds to the union of several joined-inheritance
  1025. subclasses at once.
  1026. For convenience, the :func:`.aliased` function also accepts plain
  1027. :class:`_expression.FromClause` constructs, such as a
  1028. :class:`_schema.Table` or
  1029. :func:`_expression.select` construct. In those cases, the
  1030. :meth:`_expression.FromClause.alias`
  1031. method is called on the object and the new
  1032. :class:`_expression.Alias` object returned. The returned
  1033. :class:`_expression.Alias` is not
  1034. ORM-mapped in this case.
  1035. .. seealso::
  1036. :ref:`tutorial_orm_entity_aliases` - in the :ref:`unified_tutorial`
  1037. :ref:`orm_queryguide_orm_aliases` - in the :ref:`queryguide_toplevel`
  1038. :ref:`ormtutorial_aliases` - in the legacy :ref:`ormtutorial_toplevel`
  1039. :param element: element to be aliased. Is normally a mapped class,
  1040. but for convenience can also be a :class:`_expression.FromClause`
  1041. element.
  1042. :param alias: Optional selectable unit to map the element to. This is
  1043. usually used to link the object to a subquery, and should be an aliased
  1044. select construct as one would produce from the
  1045. :meth:`_query.Query.subquery` method or
  1046. the :meth:`_expression.Select.subquery` or
  1047. :meth:`_expression.Select.alias` methods of the :func:`_expression.select`
  1048. construct.
  1049. :param name: optional string name to use for the alias, if not specified
  1050. by the ``alias`` parameter. The name, among other things, forms the
  1051. attribute name that will be accessible via tuples returned by a
  1052. :class:`_query.Query` object. Not supported when creating aliases
  1053. of :class:`_sql.Join` objects.
  1054. :param flat: Boolean, will be passed through to the
  1055. :meth:`_expression.FromClause.alias` call so that aliases of
  1056. :class:`_expression.Join` objects will alias the individual tables
  1057. inside the join, rather than creating a subquery. This is generally
  1058. supported by all modern databases with regards to right-nested joins
  1059. and generally produces more efficient queries.
  1060. :param adapt_on_names: if True, more liberal "matching" will be used when
  1061. mapping the mapped columns of the ORM entity to those of the
  1062. given selectable - a name-based match will be performed if the
  1063. given selectable doesn't otherwise have a column that corresponds
  1064. to one on the entity. The use case for this is when associating
  1065. an entity with some derived selectable such as one that uses
  1066. aggregate functions::
  1067. class UnitPrice(Base):
  1068. __tablename__ = 'unit_price'
  1069. ...
  1070. unit_id = Column(Integer)
  1071. price = Column(Numeric)
  1072. aggregated_unit_price = Session.query(
  1073. func.sum(UnitPrice.price).label('price')
  1074. ).group_by(UnitPrice.unit_id).subquery()
  1075. aggregated_unit_price = aliased(UnitPrice,
  1076. alias=aggregated_unit_price, adapt_on_names=True)
  1077. Above, functions on ``aggregated_unit_price`` which refer to
  1078. ``.price`` will return the
  1079. ``func.sum(UnitPrice.price).label('price')`` column, as it is
  1080. matched on the name "price". Ordinarily, the "price" function
  1081. wouldn't have any "column correspondence" to the actual
  1082. ``UnitPrice.price`` column as it is not a proxy of the original.
  1083. """
  1084. if isinstance(element, expression.FromClause):
  1085. if adapt_on_names:
  1086. raise sa_exc.ArgumentError(
  1087. "adapt_on_names only applies to ORM elements"
  1088. )
  1089. if name:
  1090. return element.alias(name=name, flat=flat)
  1091. else:
  1092. return coercions.expect(
  1093. roles.AnonymizedFromClauseRole, element, flat=flat
  1094. )
  1095. else:
  1096. return AliasedClass(
  1097. element,
  1098. alias=alias,
  1099. flat=flat,
  1100. name=name,
  1101. adapt_on_names=adapt_on_names,
  1102. )
  1103. def with_polymorphic(
  1104. base,
  1105. classes,
  1106. selectable=False,
  1107. flat=False,
  1108. polymorphic_on=None,
  1109. aliased=False,
  1110. innerjoin=False,
  1111. _use_mapper_path=False,
  1112. _existing_alias=None,
  1113. ):
  1114. """Produce an :class:`.AliasedClass` construct which specifies
  1115. columns for descendant mappers of the given base.
  1116. Using this method will ensure that each descendant mapper's
  1117. tables are included in the FROM clause, and will allow filter()
  1118. criterion to be used against those tables. The resulting
  1119. instances will also have those columns already loaded so that
  1120. no "post fetch" of those columns will be required.
  1121. .. seealso::
  1122. :ref:`with_polymorphic` - full discussion of
  1123. :func:`_orm.with_polymorphic`.
  1124. :param base: Base class to be aliased.
  1125. :param classes: a single class or mapper, or list of
  1126. class/mappers, which inherit from the base class.
  1127. Alternatively, it may also be the string ``'*'``, in which case
  1128. all descending mapped classes will be added to the FROM clause.
  1129. :param aliased: when True, the selectable will be aliased. For a
  1130. JOIN, this means the JOIN will be SELECTed from inside of a subquery
  1131. unless the :paramref:`_orm.with_polymorphic.flat` flag is set to
  1132. True, which is recommended for simpler use cases.
  1133. :param flat: Boolean, will be passed through to the
  1134. :meth:`_expression.FromClause.alias` call so that aliases of
  1135. :class:`_expression.Join` objects will alias the individual tables
  1136. inside the join, rather than creating a subquery. This is generally
  1137. supported by all modern databases with regards to right-nested joins
  1138. and generally produces more efficient queries. Setting this flag is
  1139. recommended as long as the resulting SQL is functional.
  1140. :param selectable: a table or subquery that will
  1141. be used in place of the generated FROM clause. This argument is
  1142. required if any of the desired classes use concrete table
  1143. inheritance, since SQLAlchemy currently cannot generate UNIONs
  1144. among tables automatically. If used, the ``selectable`` argument
  1145. must represent the full set of tables and columns mapped by every
  1146. mapped class. Otherwise, the unaccounted mapped columns will
  1147. result in their table being appended directly to the FROM clause
  1148. which will usually lead to incorrect results.
  1149. When left at its default value of ``False``, the polymorphic
  1150. selectable assigned to the base mapper is used for selecting rows.
  1151. However, it may also be passed as ``None``, which will bypass the
  1152. configured polymorphic selectable and instead construct an ad-hoc
  1153. selectable for the target classes given; for joined table inheritance
  1154. this will be a join that includes all target mappers and their
  1155. subclasses.
  1156. :param polymorphic_on: a column to be used as the "discriminator"
  1157. column for the given selectable. If not given, the polymorphic_on
  1158. attribute of the base classes' mapper will be used, if any. This
  1159. is useful for mappings that don't have polymorphic loading
  1160. behavior by default.
  1161. :param innerjoin: if True, an INNER JOIN will be used. This should
  1162. only be specified if querying for one specific subtype only
  1163. """
  1164. primary_mapper = _class_to_mapper(base)
  1165. if selectable not in (None, False) and flat:
  1166. raise sa_exc.ArgumentError(
  1167. "the 'flat' and 'selectable' arguments cannot be passed "
  1168. "simultaneously to with_polymorphic()"
  1169. )
  1170. if _existing_alias:
  1171. assert _existing_alias.mapper is primary_mapper
  1172. classes = util.to_set(classes)
  1173. new_classes = set(
  1174. [mp.class_ for mp in _existing_alias.with_polymorphic_mappers]
  1175. )
  1176. if classes == new_classes:
  1177. return _existing_alias
  1178. else:
  1179. classes = classes.union(new_classes)
  1180. mappers, selectable = primary_mapper._with_polymorphic_args(
  1181. classes, selectable, innerjoin=innerjoin
  1182. )
  1183. if aliased or flat:
  1184. selectable = selectable._anonymous_fromclause(flat=flat)
  1185. return AliasedClass(
  1186. base,
  1187. selectable,
  1188. with_polymorphic_mappers=mappers,
  1189. with_polymorphic_discriminator=polymorphic_on,
  1190. use_mapper_path=_use_mapper_path,
  1191. represents_outer_join=not innerjoin,
  1192. )
  1193. @inspection._self_inspects
  1194. class Bundle(
  1195. ORMColumnsClauseRole,
  1196. SupportsCloneAnnotations,
  1197. sql_base.MemoizedHasCacheKey,
  1198. InspectionAttr,
  1199. ):
  1200. """A grouping of SQL expressions that are returned by a :class:`.Query`
  1201. under one namespace.
  1202. The :class:`.Bundle` essentially allows nesting of the tuple-based
  1203. results returned by a column-oriented :class:`_query.Query` object.
  1204. It also
  1205. is extensible via simple subclassing, where the primary capability
  1206. to override is that of how the set of expressions should be returned,
  1207. allowing post-processing as well as custom return types, without
  1208. involving ORM identity-mapped classes.
  1209. .. versionadded:: 0.9.0
  1210. .. seealso::
  1211. :ref:`bundles`
  1212. """
  1213. single_entity = False
  1214. """If True, queries for a single Bundle will be returned as a single
  1215. entity, rather than an element within a keyed tuple."""
  1216. is_clause_element = False
  1217. is_mapper = False
  1218. is_aliased_class = False
  1219. is_bundle = True
  1220. _propagate_attrs = util.immutabledict()
  1221. def __init__(self, name, *exprs, **kw):
  1222. r"""Construct a new :class:`.Bundle`.
  1223. e.g.::
  1224. bn = Bundle("mybundle", MyClass.x, MyClass.y)
  1225. for row in session.query(bn).filter(
  1226. bn.c.x == 5).filter(bn.c.y == 4):
  1227. print(row.mybundle.x, row.mybundle.y)
  1228. :param name: name of the bundle.
  1229. :param \*exprs: columns or SQL expressions comprising the bundle.
  1230. :param single_entity=False: if True, rows for this :class:`.Bundle`
  1231. can be returned as a "single entity" outside of any enclosing tuple
  1232. in the same manner as a mapped entity.
  1233. """
  1234. self.name = self._label = name
  1235. self.exprs = exprs = [
  1236. coercions.expect(
  1237. roles.ColumnsClauseRole, expr, apply_propagate_attrs=self
  1238. )
  1239. for expr in exprs
  1240. ]
  1241. self.c = self.columns = ColumnCollection(
  1242. (getattr(col, "key", col._label), col)
  1243. for col in [e._annotations.get("bundle", e) for e in exprs]
  1244. )
  1245. self.single_entity = kw.pop("single_entity", self.single_entity)
  1246. def _gen_cache_key(self, anon_map, bindparams):
  1247. return (self.__class__, self.name, self.single_entity) + tuple(
  1248. [expr._gen_cache_key(anon_map, bindparams) for expr in self.exprs]
  1249. )
  1250. @property
  1251. def mapper(self):
  1252. return self.exprs[0]._annotations.get("parentmapper", None)
  1253. @property
  1254. def entity(self):
  1255. return self.exprs[0]._annotations.get("parententity", None)
  1256. @property
  1257. def entity_namespace(self):
  1258. return self.c
  1259. columns = None
  1260. """A namespace of SQL expressions referred to by this :class:`.Bundle`.
  1261. e.g.::
  1262. bn = Bundle("mybundle", MyClass.x, MyClass.y)
  1263. q = sess.query(bn).filter(bn.c.x == 5)
  1264. Nesting of bundles is also supported::
  1265. b1 = Bundle("b1",
  1266. Bundle('b2', MyClass.a, MyClass.b),
  1267. Bundle('b3', MyClass.x, MyClass.y)
  1268. )
  1269. q = sess.query(b1).filter(
  1270. b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)
  1271. .. seealso::
  1272. :attr:`.Bundle.c`
  1273. """
  1274. c = None
  1275. """An alias for :attr:`.Bundle.columns`."""
  1276. def _clone(self):
  1277. cloned = self.__class__.__new__(self.__class__)
  1278. cloned.__dict__.update(self.__dict__)
  1279. return cloned
  1280. def __clause_element__(self):
  1281. # ensure existing entity_namespace remains
  1282. annotations = {"bundle": self, "entity_namespace": self}
  1283. annotations.update(self._annotations)
  1284. plugin_subject = self.exprs[0]._propagate_attrs.get(
  1285. "plugin_subject", self.entity
  1286. )
  1287. return (
  1288. expression.ClauseList(
  1289. _literal_as_text_role=roles.ColumnsClauseRole,
  1290. group=False,
  1291. *[e._annotations.get("bundle", e) for e in self.exprs]
  1292. )
  1293. ._annotate(annotations)
  1294. ._set_propagate_attrs(
  1295. # the Bundle *must* use the orm plugin no matter what. the
  1296. # subject can be None but it's much better if it's not.
  1297. {
  1298. "compile_state_plugin": "orm",
  1299. "plugin_subject": plugin_subject,
  1300. }
  1301. )
  1302. )
  1303. @property
  1304. def clauses(self):
  1305. return self.__clause_element__().clauses
  1306. def label(self, name):
  1307. """Provide a copy of this :class:`.Bundle` passing a new label."""
  1308. cloned = self._clone()
  1309. cloned.name = name
  1310. return cloned
  1311. def create_row_processor(self, query, procs, labels):
  1312. """Produce the "row processing" function for this :class:`.Bundle`.
  1313. May be overridden by subclasses.
  1314. .. seealso::
  1315. :ref:`bundles` - includes an example of subclassing.
  1316. """
  1317. keyed_tuple = result_tuple(labels, [() for l in labels])
  1318. def proc(row):
  1319. return keyed_tuple([proc(row) for proc in procs])
  1320. return proc
  1321. def _orm_annotate(element, exclude=None):
  1322. """Deep copy the given ClauseElement, annotating each element with the
  1323. "_orm_adapt" flag.
  1324. Elements within the exclude collection will be cloned but not annotated.
  1325. """
  1326. return sql_util._deep_annotate(element, {"_orm_adapt": True}, exclude)
  1327. def _orm_deannotate(element):
  1328. """Remove annotations that link a column to a particular mapping.
  1329. Note this doesn't affect "remote" and "foreign" annotations
  1330. passed by the :func:`_orm.foreign` and :func:`_orm.remote`
  1331. annotators.
  1332. """
  1333. return sql_util._deep_deannotate(
  1334. element, values=("_orm_adapt", "parententity")
  1335. )
  1336. def _orm_full_deannotate(element):
  1337. return sql_util._deep_deannotate(element)
  1338. class _ORMJoin(expression.Join):
  1339. """Extend Join to support ORM constructs as input."""
  1340. __visit_name__ = expression.Join.__visit_name__
  1341. inherit_cache = True
  1342. def __init__(
  1343. self,
  1344. left,
  1345. right,
  1346. onclause=None,
  1347. isouter=False,
  1348. full=False,
  1349. _left_memo=None,
  1350. _right_memo=None,
  1351. _extra_criteria=(),
  1352. ):
  1353. left_info = inspection.inspect(left)
  1354. right_info = inspection.inspect(right)
  1355. adapt_to = right_info.selectable
  1356. # used by joined eager loader
  1357. self._left_memo = _left_memo
  1358. self._right_memo = _right_memo
  1359. # legacy, for string attr name ON clause. if that's removed
  1360. # then the "_joined_from_info" concept can go
  1361. left_orm_info = getattr(left, "_joined_from_info", left_info)
  1362. self._joined_from_info = right_info
  1363. if isinstance(onclause, util.string_types):
  1364. onclause = getattr(left_orm_info.entity, onclause)
  1365. # ####
  1366. if isinstance(onclause, attributes.QueryableAttribute):
  1367. on_selectable = onclause.comparator._source_selectable()
  1368. prop = onclause.property
  1369. _extra_criteria += onclause._extra_criteria
  1370. elif isinstance(onclause, MapperProperty):
  1371. # used internally by joined eager loader...possibly not ideal
  1372. prop = onclause
  1373. on_selectable = prop.parent.selectable
  1374. else:
  1375. prop = None
  1376. if prop:
  1377. left_selectable = left_info.selectable
  1378. if sql_util.clause_is_present(on_selectable, left_selectable):
  1379. adapt_from = on_selectable
  1380. else:
  1381. adapt_from = left_selectable
  1382. (
  1383. pj,
  1384. sj,
  1385. source,
  1386. dest,
  1387. secondary,
  1388. target_adapter,
  1389. ) = prop._create_joins(
  1390. source_selectable=adapt_from,
  1391. dest_selectable=adapt_to,
  1392. source_polymorphic=True,
  1393. of_type_entity=right_info,
  1394. alias_secondary=True,
  1395. extra_criteria=_extra_criteria,
  1396. )
  1397. if sj is not None:
  1398. if isouter:
  1399. # note this is an inner join from secondary->right
  1400. right = sql.join(secondary, right, sj)
  1401. onclause = pj
  1402. else:
  1403. left = sql.join(left, secondary, pj, isouter)
  1404. onclause = sj
  1405. else:
  1406. onclause = pj
  1407. self._target_adapter = target_adapter
  1408. augment_onclause = onclause is None and _extra_criteria
  1409. expression.Join.__init__(self, left, right, onclause, isouter, full)
  1410. if augment_onclause:
  1411. self.onclause &= sql.and_(*_extra_criteria)
  1412. if (
  1413. not prop
  1414. and getattr(right_info, "mapper", None)
  1415. and right_info.mapper.single
  1416. ):
  1417. # if single inheritance target and we are using a manual
  1418. # or implicit ON clause, augment it the same way we'd augment the
  1419. # WHERE.
  1420. single_crit = right_info.mapper._single_table_criterion
  1421. if single_crit is not None:
  1422. if right_info.is_aliased_class:
  1423. single_crit = right_info._adapter.traverse(single_crit)
  1424. self.onclause = self.onclause & single_crit
  1425. def _splice_into_center(self, other):
  1426. """Splice a join into the center.
  1427. Given join(a, b) and join(b, c), return join(a, b).join(c)
  1428. """
  1429. leftmost = other
  1430. while isinstance(leftmost, sql.Join):
  1431. leftmost = leftmost.left
  1432. assert self.right is leftmost
  1433. left = _ORMJoin(
  1434. self.left,
  1435. other.left,
  1436. self.onclause,
  1437. isouter=self.isouter,
  1438. _left_memo=self._left_memo,
  1439. _right_memo=other._left_memo,
  1440. )
  1441. return _ORMJoin(
  1442. left,
  1443. other.right,
  1444. other.onclause,
  1445. isouter=other.isouter,
  1446. _right_memo=other._right_memo,
  1447. )
  1448. def join(
  1449. self,
  1450. right,
  1451. onclause=None,
  1452. isouter=False,
  1453. full=False,
  1454. join_to_left=None,
  1455. ):
  1456. return _ORMJoin(self, right, onclause, full=full, isouter=isouter)
  1457. def outerjoin(self, right, onclause=None, full=False, join_to_left=None):
  1458. return _ORMJoin(self, right, onclause, isouter=True, full=full)
  1459. def join(
  1460. left, right, onclause=None, isouter=False, full=False, join_to_left=None
  1461. ):
  1462. r"""Produce an inner join between left and right clauses.
  1463. :func:`_orm.join` is an extension to the core join interface
  1464. provided by :func:`_expression.join()`, where the
  1465. left and right selectables may be not only core selectable
  1466. objects such as :class:`_schema.Table`, but also mapped classes or
  1467. :class:`.AliasedClass` instances. The "on" clause can
  1468. be a SQL expression, or an attribute or string name
  1469. referencing a configured :func:`_orm.relationship`.
  1470. :func:`_orm.join` is not commonly needed in modern usage,
  1471. as its functionality is encapsulated within that of the
  1472. :meth:`_query.Query.join` method, which features a
  1473. significant amount of automation beyond :func:`_orm.join`
  1474. by itself. Explicit usage of :func:`_orm.join`
  1475. with :class:`_query.Query` involves usage of the
  1476. :meth:`_query.Query.select_from` method, as in::
  1477. from sqlalchemy.orm import join
  1478. session.query(User).\
  1479. select_from(join(User, Address, User.addresses)).\
  1480. filter(Address.email_address=='foo@bar.com')
  1481. In modern SQLAlchemy the above join can be written more
  1482. succinctly as::
  1483. session.query(User).\
  1484. join(User.addresses).\
  1485. filter(Address.email_address=='foo@bar.com')
  1486. See :meth:`_query.Query.join` for information on modern usage
  1487. of ORM level joins.
  1488. .. deprecated:: 0.8
  1489. the ``join_to_left`` parameter is deprecated, and will be removed
  1490. in a future release. The parameter has no effect.
  1491. """
  1492. return _ORMJoin(left, right, onclause, isouter, full)
  1493. def outerjoin(left, right, onclause=None, full=False, join_to_left=None):
  1494. """Produce a left outer join between left and right clauses.
  1495. This is the "outer join" version of the :func:`_orm.join` function,
  1496. featuring the same behavior except that an OUTER JOIN is generated.
  1497. See that function's documentation for other usage details.
  1498. """
  1499. return _ORMJoin(left, right, onclause, True, full)
  1500. def with_parent(instance, prop, from_entity=None):
  1501. """Create filtering criterion that relates this query's primary entity
  1502. to the given related instance, using established
  1503. :func:`_orm.relationship()`
  1504. configuration.
  1505. E.g.::
  1506. stmt = select(Address).where(with_parent(some_user, User.addresses))
  1507. The SQL rendered is the same as that rendered when a lazy loader
  1508. would fire off from the given parent on that attribute, meaning
  1509. that the appropriate state is taken from the parent object in
  1510. Python without the need to render joins to the parent table
  1511. in the rendered statement.
  1512. The given property may also make use of :meth:`_orm.PropComparator.of_type`
  1513. to indicate the left side of the criteria::
  1514. a1 = aliased(Address)
  1515. a2 = aliased(Address)
  1516. stmt = select(a1, a2).where(
  1517. with_parent(u1, User.addresses.of_type(a2))
  1518. )
  1519. The above use is equivalent to using the
  1520. :func:`_orm.with_parent.from_entity` argument::
  1521. a1 = aliased(Address)
  1522. a2 = aliased(Address)
  1523. stmt = select(a1, a2).where(
  1524. with_parent(u1, User.addresses, from_entity=a2)
  1525. )
  1526. :param instance:
  1527. An instance which has some :func:`_orm.relationship`.
  1528. :param property:
  1529. String property name, or class-bound attribute, which indicates
  1530. what relationship from the instance should be used to reconcile the
  1531. parent/child relationship.
  1532. .. deprecated:: 1.4 Using strings is deprecated and will be removed
  1533. in SQLAlchemy 2.0. Please use the class-bound attribute directly.
  1534. :param from_entity:
  1535. Entity in which to consider as the left side. This defaults to the
  1536. "zero" entity of the :class:`_query.Query` itself.
  1537. .. versionadded:: 1.2
  1538. """
  1539. if isinstance(prop, util.string_types):
  1540. util.warn_deprecated_20(
  1541. "Using strings to indicate relationship names in the ORM "
  1542. "with_parent() function is deprecated and will be removed "
  1543. "SQLAlchemy 2.0. Please use the class-bound attribute directly."
  1544. )
  1545. mapper = object_mapper(instance)
  1546. prop = getattr(mapper.class_, prop).property
  1547. elif isinstance(prop, attributes.QueryableAttribute):
  1548. if prop._of_type:
  1549. from_entity = prop._of_type
  1550. prop = prop.property
  1551. return prop._with_parent(instance, from_entity=from_entity)
  1552. def has_identity(object_):
  1553. """Return True if the given object has a database
  1554. identity.
  1555. This typically corresponds to the object being
  1556. in either the persistent or detached state.
  1557. .. seealso::
  1558. :func:`.was_deleted`
  1559. """
  1560. state = attributes.instance_state(object_)
  1561. return state.has_identity
  1562. def was_deleted(object_):
  1563. """Return True if the given object was deleted
  1564. within a session flush.
  1565. This is regardless of whether or not the object is
  1566. persistent or detached.
  1567. .. seealso::
  1568. :attr:`.InstanceState.was_deleted`
  1569. """
  1570. state = attributes.instance_state(object_)
  1571. return state.was_deleted
  1572. def _entity_corresponds_to(given, entity):
  1573. """determine if 'given' corresponds to 'entity', in terms
  1574. of an entity passed to Query that would match the same entity
  1575. being referred to elsewhere in the query.
  1576. """
  1577. if entity.is_aliased_class:
  1578. if given.is_aliased_class:
  1579. if entity._base_alias() is given._base_alias():
  1580. return True
  1581. return False
  1582. elif given.is_aliased_class:
  1583. if given._use_mapper_path:
  1584. return entity in given.with_polymorphic_mappers
  1585. else:
  1586. return entity is given
  1587. return entity.common_parent(given)
  1588. def _entity_corresponds_to_use_path_impl(given, entity):
  1589. """determine if 'given' corresponds to 'entity', in terms
  1590. of a path of loader options where a mapped attribute is taken to
  1591. be a member of a parent entity.
  1592. e.g.::
  1593. someoption(A).someoption(A.b) # -> fn(A, A) -> True
  1594. someoption(A).someoption(C.d) # -> fn(A, C) -> False
  1595. a1 = aliased(A)
  1596. someoption(a1).someoption(A.b) # -> fn(a1, A) -> False
  1597. someoption(a1).someoption(a1.b) # -> fn(a1, a1) -> True
  1598. wp = with_polymorphic(A, [A1, A2])
  1599. someoption(wp).someoption(A1.foo) # -> fn(wp, A1) -> False
  1600. someoption(wp).someoption(wp.A1.foo) # -> fn(wp, wp.A1) -> True
  1601. """
  1602. if given.is_aliased_class:
  1603. return (
  1604. entity.is_aliased_class
  1605. and not entity._use_mapper_path
  1606. and (given is entity or given in entity._with_polymorphic_entities)
  1607. )
  1608. elif not entity.is_aliased_class:
  1609. return given.common_parent(entity.mapper)
  1610. else:
  1611. return (
  1612. entity._use_mapper_path
  1613. and given in entity.with_polymorphic_mappers
  1614. )
  1615. def _entity_isa(given, mapper):
  1616. """determine if 'given' "is a" mapper, in terms of the given
  1617. would load rows of type 'mapper'.
  1618. """
  1619. if given.is_aliased_class:
  1620. return mapper in given.with_polymorphic_mappers or given.mapper.isa(
  1621. mapper
  1622. )
  1623. elif given.with_polymorphic_mappers:
  1624. return mapper in given.with_polymorphic_mappers
  1625. else:
  1626. return given.isa(mapper)
  1627. def randomize_unitofwork():
  1628. """Use random-ordering sets within the unit of work in order
  1629. to detect unit of work sorting issues.
  1630. This is a utility function that can be used to help reproduce
  1631. inconsistent unit of work sorting issues. For example,
  1632. if two kinds of objects A and B are being inserted, and
  1633. B has a foreign key reference to A - the A must be inserted first.
  1634. However, if there is no relationship between A and B, the unit of work
  1635. won't know to perform this sorting, and an operation may or may not
  1636. fail, depending on how the ordering works out. Since Python sets
  1637. and dictionaries have non-deterministic ordering, such an issue may
  1638. occur on some runs and not on others, and in practice it tends to
  1639. have a great dependence on the state of the interpreter. This leads
  1640. to so-called "heisenbugs" where changing entirely irrelevant aspects
  1641. of the test program still cause the failure behavior to change.
  1642. By calling ``randomize_unitofwork()`` when a script first runs, the
  1643. ordering of a key series of sets within the unit of work implementation
  1644. are randomized, so that the script can be minimized down to the
  1645. fundamental mapping and operation that's failing, while still reproducing
  1646. the issue on at least some runs.
  1647. This utility is also available when running the test suite via the
  1648. ``--reversetop`` flag.
  1649. """
  1650. from sqlalchemy.orm import unitofwork, session, mapper, dependency
  1651. from sqlalchemy.util import topological
  1652. from sqlalchemy.testing.util import RandomSet
  1653. topological.set = (
  1654. unitofwork.set
  1655. ) = session.set = mapper.set = dependency.set = RandomSet
  1656. def _getitem(iterable_query, item, allow_negative):
  1657. """calculate __getitem__ in terms of an iterable query object
  1658. that also has a slice() method.
  1659. """
  1660. def _no_negative_indexes():
  1661. if not allow_negative:
  1662. raise IndexError(
  1663. "negative indexes are not accepted by SQL "
  1664. "index / slice operators"
  1665. )
  1666. else:
  1667. util.warn_deprecated_20(
  1668. "Support for negative indexes for SQL index / slice operators "
  1669. "will be "
  1670. "removed in 2.0; these operators fetch the complete result "
  1671. "and do not work efficiently."
  1672. )
  1673. if isinstance(item, slice):
  1674. start, stop, step = util.decode_slice(item)
  1675. if (
  1676. isinstance(stop, int)
  1677. and isinstance(start, int)
  1678. and stop - start <= 0
  1679. ):
  1680. return []
  1681. elif (isinstance(start, int) and start < 0) or (
  1682. isinstance(stop, int) and stop < 0
  1683. ):
  1684. _no_negative_indexes()
  1685. return list(iterable_query)[item]
  1686. res = iterable_query.slice(start, stop)
  1687. if step is not None:
  1688. return list(res)[None : None : item.step]
  1689. else:
  1690. return list(res)
  1691. else:
  1692. if item == -1:
  1693. _no_negative_indexes()
  1694. return list(iterable_query)[-1]
  1695. else:
  1696. return list(iterable_query[item : item + 1])[0]