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