Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/util.py: 29%
601 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
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
9import re
10import types
11import weakref
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
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)
64class CascadeOptions(frozenset):
65 """Keeps track of the options sent to
66 :paramref:`.relationship.cascade`"""
68 _add_w_all_cascades = all_cascades.difference(
69 ["all", "none", "delete-orphan"]
70 )
71 _allowed_cascades = all_cascades
73 _viewonly_cascades = ["expunge", "all", "none", "refresh-expire", "merge"]
75 __slots__ = (
76 "save_update",
77 "delete",
78 "refresh_expire",
79 "merge",
80 "expunge",
81 "delete_orphan",
82 )
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 )
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")
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
115 if self.delete_orphan and not self.delete:
116 util.warn(
117 "The 'delete-orphan' cascade " "option requires 'delete'."
118 )
119 return self
121 def __repr__(self):
122 return "CascadeOptions(%r)" % (",".join([x for x in sorted(self)]))
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)
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 """
135 if not include_backrefs:
137 def detect_is_backref(state, initiator):
138 impl = state.manager[key].impl
139 return initiator.impl is not impl
141 if include_removes:
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
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 ]
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
164 def remove(state, value, initiator):
165 if include_backrefs or not detect_is_backref(state, initiator):
166 validator(state.obj(), key, value, True)
168 else:
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
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]
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
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)
196def polymorphic_union(
197 table_map, typecolname, aliasname="p_union", cast_nulls=True
198):
199 """Create a ``UNION`` statement used by a polymorphic mapper.
201 See :ref:`concrete_inheritance` for an example of how
202 this is used.
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.
216 """
218 colnames = util.OrderedSet()
219 colnamemaps = {}
220 types = {}
221 for key in table_map:
222 table = table_map[key]
224 table = coercions.expect(
225 roles.StrictFromClauseRole, table, allow_select=True
226 )
227 table_map[key] = table
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
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)
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)
278def identity_key(*args, **kwargs):
279 r"""Generate "identity key" tuples, as are used as keys in the
280 :attr:`.Session.identity_map` dictionary.
282 This function has several call styles:
284 * ``identity_key(class, ident, identity_token=token)``
286 This form receives a mapped class and a primary key scalar or
287 tuple as an argument.
289 E.g.::
291 >>> identity_key(MyClass, (1, 2))
292 (<class '__main__.MyClass'>, (1, 2), None)
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
298 .. versionadded:: 1.2 added identity_token
301 * ``identity_key(instance=instance)``
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).
308 E.g.::
310 >>> instance = MyClass(1, 2)
311 >>> identity_key(instance=instance)
312 (<class '__main__.MyClass'>, (1, 2), None)
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.
319 :param instance: object instance (must be given as a keyword arg)
321 * ``identity_key(class, row=row, identity_token=token)``
323 This form is similar to the class/tuple form, except is passed a
324 database result row as a :class:`.Row` object.
326 E.g.::
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)
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
339 .. versionadded:: 1.2 added identity_token
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 )
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)
382class ORMAdapter(sql_util.ColumnAdapter):
383 """ColumnAdapter subclass which excludes adaptation of entities from
384 non-matching mappers.
386 """
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)
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
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 )
416 def _include_fn(self, elem):
417 entity = elem._annotations.get("parentmapper", None)
419 return not entity or entity.isa(self.mapper) or self.mapper.isa(entity)
422class AliasedClass(object):
423 r"""Represents an "aliased" form of a mapped class for usage with Query.
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.
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::
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)
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.
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.
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`).
457 The :class:`.AliasedClass` can be inspected for its underlying
458 :class:`_orm.Mapper`, aliased selectable, and other information
459 using :func:`_sa.inspect`::
461 from sqlalchemy import inspect
462 my_alias = aliased(MyClass)
463 insp = inspect(my_alias)
465 The resulting inspection object is an instance of :class:`.AliasedInsp`.
468 .. seealso::
470 :func:`.aliased`
472 :func:`.with_polymorphic`
474 :ref:`relationship_aliased_class`
476 :ref:`relationship_to_window_function`
479 """
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
498 nest_adapters = False
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
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 )
531 self.__name__ = "AliasedClass_%s" % mapper.class_.__name__
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
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)
547 return obj
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)
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)
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)
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)
576 return attr
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)
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)
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)
597 return attr
599 def __repr__(self):
600 return "<AliasedClass at 0x%x; %s>" % (
601 id(self),
602 self._aliased_insp._target.__name__,
603 )
605 def __str__(self):
606 return str(self._aliased_insp)
609class AliasedInsp(
610 ORMEntityColumnsClauseRole,
611 ORMFromClauseRole,
612 sql_base.MemoizedHasCacheKey,
613 InspectionAttr,
614):
615 """Provide an inspection interface for an
616 :class:`.AliasedClass` object.
618 The :class:`.AliasedInsp` object is returned
619 given an :class:`.AliasedClass` using the
620 :func:`_sa.inspect` function::
622 from sqlalchemy import inspect
623 from sqlalchemy.orm import aliased
625 my_alias = aliased(MyMappedClass)
626 insp = inspect(my_alias)
628 Attributes on :class:`.AliasedInsp`
629 include:
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.
647 .. seealso::
649 :ref:`inspection_toplevel`
651 """
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 ]
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 ):
681 mapped_class_or_ac = inspected.entity
682 mapper = inspected.mapper
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
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 )
710 setattr(self.entity, poly.class_.__name__, ent)
711 self._with_polymorphic_entities.append(ent._aliased_insp)
713 else:
714 self._is_with_polymorphic = False
715 self.with_polymorphic_mappers = [mapper]
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 )
732 if nest_adapters:
733 self._adapter = inspected._adapter.wrap(self._adapter)
735 self._adapt_on_names = adapt_on_names
736 self._target = mapped_class_or_ac
737 # self._target = mapper.class_ # mapped_class_or_ac
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
753 is_aliased_class = True
754 "always returns True"
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 )
768 @property
769 def entity_namespace(self):
770 return self.entity
772 @property
773 def class_(self):
774 """Return the mapped class ultimately represented by this
775 :class:`.AliasedInsp`."""
776 return self.mapper.class_
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)
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 }
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 )
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 )
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)
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 )
855 @util.memoized_property
856 def _memoized_values(self):
857 return {}
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()
868 cols_plus_keys = [
869 (key, self._adapt_element(col)) for key, col in cols_plus_keys
870 ]
872 return ColumnCollection(cols_plus_keys)
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
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 )
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__,)
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.
913 might also be useful for other per-lambda instrumentations should
914 the need arise.
916 """
918 __slots__ = ("subject",)
920 def __init__(self, subject):
921 self.subject = subject
923 @util.preload_module("sqlalchemy.orm.decl_api")
924 def __getattribute__(self, name):
925 decl_api = util.preloaded.orm.decl_api
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)
936class LoaderCriteriaOption(CriteriaOption):
937 """Add additional WHERE criteria to the load for all occurrences of
938 a particular entity.
940 :class:`_orm.LoaderCriteriaOption` is invoked using the
941 :func:`_orm.with_loader_criteria` function; see that function for
942 details.
944 .. versionadded:: 1.4
946 """
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 ]
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.
968 .. versionadded:: 1.4
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.
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::
984 from sqlalchemy.orm import with_loader_criteria
986 stmt = select(User).options(
987 selectinload(User.addresses),
988 with_loader_criteria(Address, Address.email_address != 'foo'))
989 )
991 Above, the "selectinload" for ``User.addresses`` will apply the
992 given filtering criteria to the WHERE clause.
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::
998 q = session.query(User).outerjoin(User.addresses).options(
999 with_loader_criteria(Address, Address.email_address != 'foo'))
1000 )
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`::
1010 session = Session(bind=engine)
1012 @event.listens_for("do_orm_execute", session)
1013 def _add_filtering_criteria(execute_state):
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 )
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.
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.
1041 .. tip::
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.
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::
1061 stmt = select(A).join(A.bs).options(
1062 contains_eager(A.bs),
1063 with_loader_criteria(B, B.flag == 1)
1064 )
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::
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
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.
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.
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.
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.
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.
1109 :param include_aliases: if True, apply the rule to :func:`_orm.aliased`
1110 constructs as well.
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.
1118 .. seealso::
1120 :ref:`examples_session_orm_events` - includes examples of using
1121 :func:`_orm.with_loader_criteria`.
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.
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.
1133 .. versionadded:: 1.4.0b2
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
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 )
1167 self.include_aliases = include_aliases
1168 self.propagate_to_loaders = propagate_to_loaders
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 )
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 )
1192 def _all_mappers(self):
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__())
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
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 )
1227 def process_compile_state_replaced_entities(
1228 self, compile_state, mapper_entities
1229 ):
1230 return self.process_compile_state(compile_state)
1232 def process_compile_state(self, compile_state):
1233 """Apply a modification to a given :class:`.CompileState`."""
1235 # if options to limit the criteria to immediate query only,
1236 # use compile_state.attributes instead
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)
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 )
1253 load_criteria.append(self)
1256inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
1257inspection._inspects(AliasedInsp)(lambda target: target)
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.
1264 E.g.::
1266 my_alias = aliased(MyClass)
1268 session.query(MyClass, my_alias).filter(MyClass.id > my_alias.id)
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.
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.
1292 .. seealso::
1294 :ref:`tutorial_orm_entity_aliases` - in the :ref:`unified_tutorial`
1296 :ref:`orm_queryguide_orm_aliases` - in the :ref:`queryguide_toplevel`
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.
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.
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.
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.
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::
1331 class UnitPrice(Base):
1332 __tablename__ = 'unit_price'
1333 ...
1334 unit_id = Column(Integer)
1335 price = Column(Numeric)
1337 aggregated_unit_price = Session.query(
1338 func.sum(UnitPrice.price).label('price')
1339 ).group_by(UnitPrice.unit_id).subquery()
1341 aggregated_unit_price = aliased(UnitPrice,
1342 alias=aggregated_unit_price, adapt_on_names=True)
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.
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 )
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.
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.
1394 .. seealso::
1396 :ref:`with_polymorphic` - full discussion of
1397 :func:`_orm.with_polymorphic`.
1399 :param base: Base class to be aliased.
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.
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.
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.
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.
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.
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.
1443 :param innerjoin: if True, an INNER JOIN will be used. This should
1444 only be specified if querying for one specific subtype only
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.
1452 .. versionadded:: 1.4.33
1454 """
1455 primary_mapper = _class_to_mapper(base)
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 )
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 )
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.
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.
1507 .. versionadded:: 0.9.0
1509 .. seealso::
1511 :ref:`bundles`
1514 """
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."""
1520 is_clause_element = False
1522 is_mapper = False
1524 is_aliased_class = False
1526 is_bundle = True
1528 _propagate_attrs = util.immutabledict()
1530 def __init__(self, name, *exprs, **kw):
1531 r"""Construct a new :class:`.Bundle`.
1533 e.g.::
1535 bn = Bundle("mybundle", MyClass.x, MyClass.y)
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)
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.
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 ]
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)
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 )
1567 @property
1568 def mapper(self):
1569 return self.exprs[0]._annotations.get("parentmapper", None)
1571 @property
1572 def entity(self):
1573 return self.exprs[0]._annotations.get("parententity", None)
1575 @property
1576 def entity_namespace(self):
1577 return self.c
1579 columns = None
1580 """A namespace of SQL expressions referred to by this :class:`.Bundle`.
1582 e.g.::
1584 bn = Bundle("mybundle", MyClass.x, MyClass.y)
1586 q = sess.query(bn).filter(bn.c.x == 5)
1588 Nesting of bundles is also supported::
1590 b1 = Bundle("b1",
1591 Bundle('b2', MyClass.a, MyClass.b),
1592 Bundle('b3', MyClass.x, MyClass.y)
1593 )
1595 q = sess.query(b1).filter(
1596 b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)
1598 .. seealso::
1600 :attr:`.Bundle.c`
1602 """
1604 c = None
1605 """An alias for :attr:`.Bundle.columns`."""
1607 def _clone(self):
1608 cloned = self.__class__.__new__(self.__class__)
1609 cloned.__dict__.update(self.__dict__)
1610 return cloned
1612 def __clause_element__(self):
1613 # ensure existing entity_namespace remains
1614 annotations = {"bundle": self, "entity_namespace": self}
1615 annotations.update(self._annotations)
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 )
1637 @property
1638 def clauses(self):
1639 return self.__clause_element__().clauses
1641 def label(self, name):
1642 """Provide a copy of this :class:`.Bundle` passing a new label."""
1644 cloned = self._clone()
1645 cloned.name = name
1646 return cloned
1648 def create_row_processor(self, query, procs, labels):
1649 """Produce the "row processing" function for this :class:`.Bundle`.
1651 May be overridden by subclasses.
1653 .. seealso::
1655 :ref:`bundles` - includes an example of subclassing.
1657 """
1658 keyed_tuple = result_tuple(labels, [() for l in labels])
1660 def proc(row):
1661 return keyed_tuple([proc(row) for proc in procs])
1663 return proc
1666def _orm_annotate(element, exclude=None):
1667 """Deep copy the given ClauseElement, annotating each element with the
1668 "_orm_adapt" flag.
1670 Elements within the exclude collection will be cloned but not annotated.
1672 """
1673 return sql_util._deep_annotate(element, {"_orm_adapt": True}, exclude)
1676def _orm_deannotate(element):
1677 """Remove annotations that link a column to a particular mapping.
1679 Note this doesn't affect "remote" and "foreign" annotations
1680 passed by the :func:`_orm.foreign` and :func:`_orm.remote`
1681 annotators.
1683 """
1685 return sql_util._deep_deannotate(
1686 element, values=("_orm_adapt", "parententity")
1687 )
1690def _orm_full_deannotate(element):
1691 return sql_util._deep_deannotate(element)
1694class _ORMJoin(expression.Join):
1695 """Extend Join to support ORM constructs as input."""
1697 __visit_name__ = expression.Join.__visit_name__
1699 inherit_cache = True
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)
1714 right_info = inspection.inspect(right)
1715 adapt_to = right_info.selectable
1717 # used by joined eager loader
1718 self._left_memo = _left_memo
1719 self._right_memo = _right_memo
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 # ####
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
1740 if prop:
1741 left_selectable = left_info.selectable
1743 if sql_util.clause_is_present(on_selectable, left_selectable):
1744 adapt_from = on_selectable
1745 else:
1746 adapt_from = left_selectable
1748 (
1749 pj,
1750 sj,
1751 source,
1752 dest,
1753 secondary,
1754 target_adapter,
1755 ) = prop._create_joins(
1756 source_selectable=adapt_from,
1757 dest_selectable=adapt_to,
1758 source_polymorphic=True,
1759 of_type_entity=right_info,
1760 alias_secondary=True,
1761 extra_criteria=_extra_criteria,
1762 )
1764 if sj is not None:
1765 if isouter:
1766 # note this is an inner join from secondary->right
1767 right = sql.join(secondary, right, sj)
1768 onclause = pj
1769 else:
1770 left = sql.join(left, secondary, pj, isouter)
1771 onclause = sj
1772 else:
1773 onclause = pj
1775 self._target_adapter = target_adapter
1777 # we don't use the normal coercions logic for _ORMJoin
1778 # (probably should), so do some gymnastics to get the entity.
1779 # logic here is for #8721, which was a major bug in 1.4
1780 # for almost two years, not reported/fixed until 1.4.43 (!)
1781 if left_info.is_selectable:
1782 parententity = left_selectable._annotations.get(
1783 "parententity", None
1784 )
1785 elif left_info.is_mapper or left_info.is_aliased_class:
1786 parententity = left_info
1787 else:
1788 parententity = None
1790 if parententity is not None:
1791 self._annotations = self._annotations.union(
1792 {"parententity": parententity}
1793 )
1795 augment_onclause = onclause is None and _extra_criteria
1796 expression.Join.__init__(self, left, right, onclause, isouter, full)
1798 if augment_onclause:
1799 self.onclause &= sql.and_(*_extra_criteria)
1801 if (
1802 not prop
1803 and getattr(right_info, "mapper", None)
1804 and right_info.mapper.single
1805 ):
1806 # if single inheritance target and we are using a manual
1807 # or implicit ON clause, augment it the same way we'd augment the
1808 # WHERE.
1809 single_crit = right_info.mapper._single_table_criterion
1810 if single_crit is not None:
1811 if right_info.is_aliased_class:
1812 single_crit = right_info._adapter.traverse(single_crit)
1813 self.onclause = self.onclause & single_crit
1815 def _splice_into_center(self, other):
1816 """Splice a join into the center.
1818 Given join(a, b) and join(b, c), return join(a, b).join(c)
1820 """
1821 leftmost = other
1822 while isinstance(leftmost, sql.Join):
1823 leftmost = leftmost.left
1825 assert self.right is leftmost
1827 left = _ORMJoin(
1828 self.left,
1829 other.left,
1830 self.onclause,
1831 isouter=self.isouter,
1832 _left_memo=self._left_memo,
1833 _right_memo=other._left_memo,
1834 )
1836 return _ORMJoin(
1837 left,
1838 other.right,
1839 other.onclause,
1840 isouter=other.isouter,
1841 _right_memo=other._right_memo,
1842 )
1844 def join(
1845 self,
1846 right,
1847 onclause=None,
1848 isouter=False,
1849 full=False,
1850 join_to_left=None,
1851 ):
1852 return _ORMJoin(self, right, onclause, full=full, isouter=isouter)
1854 def outerjoin(self, right, onclause=None, full=False, join_to_left=None):
1855 return _ORMJoin(self, right, onclause, isouter=True, full=full)
1858def join(
1859 left, right, onclause=None, isouter=False, full=False, join_to_left=None
1860):
1861 r"""Produce an inner join between left and right clauses.
1863 :func:`_orm.join` is an extension to the core join interface
1864 provided by :func:`_expression.join()`, where the
1865 left and right selectables may be not only core selectable
1866 objects such as :class:`_schema.Table`, but also mapped classes or
1867 :class:`.AliasedClass` instances. The "on" clause can
1868 be a SQL expression or an ORM mapped attribute
1869 referencing a configured :func:`_orm.relationship`.
1871 .. deprecated:: 1.4 using a string relationship name for the "onclause"
1872 is deprecated and will be removed in 2.0; the onclause may be only
1873 an ORM-mapped relationship attribute or a SQL expression construct.
1875 :func:`_orm.join` is not commonly needed in modern usage,
1876 as its functionality is encapsulated within that of the
1877 :meth:`_sql.Select.join` and :meth:`_query.Query.join`
1878 methods. which feature a
1879 significant amount of automation beyond :func:`_orm.join`
1880 by itself. Explicit use of :func:`_orm.join`
1881 with ORM-enabled SELECT statements involves use of the
1882 :meth:`_sql.Select.select_from` method, as in::
1884 from sqlalchemy.orm import join
1885 stmt = select(User).\
1886 select_from(join(User, Address, User.addresses)).\
1887 filter(Address.email_address=='foo@bar.com')
1889 In modern SQLAlchemy the above join can be written more
1890 succinctly as::
1892 stmt = select(User).\
1893 join(User.addresses).\
1894 filter(Address.email_address=='foo@bar.com')
1896 .. warning:: using :func:`_orm.join` directly may not work properly
1897 with modern ORM options such as :func:`_orm.with_loader_criteria`.
1898 It is strongly recommended to use the idiomatic join patterns
1899 provided by methods such as :meth:`.Select.join` and
1900 :meth:`.Select.join_from` when creating ORM joins.
1902 .. seealso::
1904 :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel` for
1905 background on idiomatic ORM join patterns
1907 """
1908 return _ORMJoin(left, right, onclause, isouter, full)
1911def outerjoin(left, right, onclause=None, full=False, join_to_left=None):
1912 """Produce a left outer join between left and right clauses.
1914 This is the "outer join" version of the :func:`_orm.join` function,
1915 featuring the same behavior except that an OUTER JOIN is generated.
1916 See that function's documentation for other usage details.
1918 """
1919 return _ORMJoin(left, right, onclause, True, full)
1922def with_parent(instance, prop, from_entity=None):
1923 """Create filtering criterion that relates this query's primary entity
1924 to the given related instance, using established
1925 :func:`_orm.relationship()`
1926 configuration.
1928 E.g.::
1930 stmt = select(Address).where(with_parent(some_user, User.addresses))
1933 The SQL rendered is the same as that rendered when a lazy loader
1934 would fire off from the given parent on that attribute, meaning
1935 that the appropriate state is taken from the parent object in
1936 Python without the need to render joins to the parent table
1937 in the rendered statement.
1939 The given property may also make use of :meth:`_orm.PropComparator.of_type`
1940 to indicate the left side of the criteria::
1943 a1 = aliased(Address)
1944 a2 = aliased(Address)
1945 stmt = select(a1, a2).where(
1946 with_parent(u1, User.addresses.of_type(a2))
1947 )
1949 The above use is equivalent to using the
1950 :func:`_orm.with_parent.from_entity` argument::
1952 a1 = aliased(Address)
1953 a2 = aliased(Address)
1954 stmt = select(a1, a2).where(
1955 with_parent(u1, User.addresses, from_entity=a2)
1956 )
1958 :param instance:
1959 An instance which has some :func:`_orm.relationship`.
1961 :param property:
1962 String property name, or class-bound attribute, which indicates
1963 what relationship from the instance should be used to reconcile the
1964 parent/child relationship.
1966 .. deprecated:: 1.4 Using strings is deprecated and will be removed
1967 in SQLAlchemy 2.0. Please use the class-bound attribute directly.
1969 :param from_entity:
1970 Entity in which to consider as the left side. This defaults to the
1971 "zero" entity of the :class:`_query.Query` itself.
1973 .. versionadded:: 1.2
1975 """
1976 if isinstance(prop, util.string_types):
1977 util.warn_deprecated_20(
1978 "Using strings to indicate relationship names in the ORM "
1979 "with_parent() function is deprecated and will be removed "
1980 "SQLAlchemy 2.0. Please use the class-bound attribute directly."
1981 )
1982 mapper = object_mapper(instance)
1983 prop = getattr(mapper.class_, prop).property
1984 elif isinstance(prop, attributes.QueryableAttribute):
1985 if prop._of_type:
1986 from_entity = prop._of_type
1987 prop = prop.property
1989 return prop._with_parent(instance, from_entity=from_entity)
1992def has_identity(object_):
1993 """Return True if the given object has a database
1994 identity.
1996 This typically corresponds to the object being
1997 in either the persistent or detached state.
1999 .. seealso::
2001 :func:`.was_deleted`
2003 """
2004 state = attributes.instance_state(object_)
2005 return state.has_identity
2008def was_deleted(object_):
2009 """Return True if the given object was deleted
2010 within a session flush.
2012 This is regardless of whether or not the object is
2013 persistent or detached.
2015 .. seealso::
2017 :attr:`.InstanceState.was_deleted`
2019 """
2021 state = attributes.instance_state(object_)
2022 return state.was_deleted
2025def _entity_corresponds_to(given, entity):
2026 """determine if 'given' corresponds to 'entity', in terms
2027 of an entity passed to Query that would match the same entity
2028 being referred to elsewhere in the query.
2030 """
2031 if entity.is_aliased_class:
2032 if given.is_aliased_class:
2033 if entity._base_alias() is given._base_alias():
2034 return True
2035 return False
2036 elif given.is_aliased_class:
2037 if given._use_mapper_path:
2038 return entity in given.with_polymorphic_mappers
2039 else:
2040 return entity is given
2042 return entity.common_parent(given)
2045def _entity_corresponds_to_use_path_impl(given, entity):
2046 """determine if 'given' corresponds to 'entity', in terms
2047 of a path of loader options where a mapped attribute is taken to
2048 be a member of a parent entity.
2050 e.g.::
2052 someoption(A).someoption(A.b) # -> fn(A, A) -> True
2053 someoption(A).someoption(C.d) # -> fn(A, C) -> False
2055 a1 = aliased(A)
2056 someoption(a1).someoption(A.b) # -> fn(a1, A) -> False
2057 someoption(a1).someoption(a1.b) # -> fn(a1, a1) -> True
2059 wp = with_polymorphic(A, [A1, A2])
2060 someoption(wp).someoption(A1.foo) # -> fn(wp, A1) -> False
2061 someoption(wp).someoption(wp.A1.foo) # -> fn(wp, wp.A1) -> True
2064 """
2065 if given.is_aliased_class:
2066 return (
2067 entity.is_aliased_class
2068 and not entity._use_mapper_path
2069 and (given is entity or given in entity._with_polymorphic_entities)
2070 )
2071 elif not entity.is_aliased_class:
2072 return given.common_parent(entity.mapper)
2073 else:
2074 return (
2075 entity._use_mapper_path
2076 and given in entity.with_polymorphic_mappers
2077 )
2080def _entity_isa(given, mapper):
2081 """determine if 'given' "is a" mapper, in terms of the given
2082 would load rows of type 'mapper'.
2084 """
2085 if given.is_aliased_class:
2086 return mapper in given.with_polymorphic_mappers or given.mapper.isa(
2087 mapper
2088 )
2089 elif given.with_polymorphic_mappers:
2090 return mapper in given.with_polymorphic_mappers
2091 else:
2092 return given.isa(mapper)
2095def randomize_unitofwork():
2096 """Use random-ordering sets within the unit of work in order
2097 to detect unit of work sorting issues.
2099 This is a utility function that can be used to help reproduce
2100 inconsistent unit of work sorting issues. For example,
2101 if two kinds of objects A and B are being inserted, and
2102 B has a foreign key reference to A - the A must be inserted first.
2103 However, if there is no relationship between A and B, the unit of work
2104 won't know to perform this sorting, and an operation may or may not
2105 fail, depending on how the ordering works out. Since Python sets
2106 and dictionaries have non-deterministic ordering, such an issue may
2107 occur on some runs and not on others, and in practice it tends to
2108 have a great dependence on the state of the interpreter. This leads
2109 to so-called "heisenbugs" where changing entirely irrelevant aspects
2110 of the test program still cause the failure behavior to change.
2112 By calling ``randomize_unitofwork()`` when a script first runs, the
2113 ordering of a key series of sets within the unit of work implementation
2114 are randomized, so that the script can be minimized down to the
2115 fundamental mapping and operation that's failing, while still reproducing
2116 the issue on at least some runs.
2118 This utility is also available when running the test suite via the
2119 ``--reversetop`` flag.
2121 """
2122 from sqlalchemy.orm import unitofwork, session, mapper, dependency
2123 from sqlalchemy.util import topological
2124 from sqlalchemy.testing.util import RandomSet
2126 topological.set = (
2127 unitofwork.set
2128 ) = session.set = mapper.set = dependency.set = RandomSet
2131def _getitem(iterable_query, item, allow_negative):
2132 """calculate __getitem__ in terms of an iterable query object
2133 that also has a slice() method.
2135 """
2137 def _no_negative_indexes():
2138 if not allow_negative:
2139 raise IndexError(
2140 "negative indexes are not accepted by SQL "
2141 "index / slice operators"
2142 )
2143 else:
2144 util.warn_deprecated_20(
2145 "Support for negative indexes for SQL index / slice operators "
2146 "will be "
2147 "removed in 2.0; these operators fetch the complete result "
2148 "and do not work efficiently."
2149 )
2151 if isinstance(item, slice):
2152 start, stop, step = util.decode_slice(item)
2154 if (
2155 isinstance(stop, int)
2156 and isinstance(start, int)
2157 and stop - start <= 0
2158 ):
2159 return []
2161 elif (isinstance(start, int) and start < 0) or (
2162 isinstance(stop, int) and stop < 0
2163 ):
2164 _no_negative_indexes()
2165 return list(iterable_query)[item]
2167 res = iterable_query.slice(start, stop)
2168 if step is not None:
2169 return list(res)[None : None : item.step]
2170 else:
2171 return list(res)
2172 else:
2173 if item == -1:
2174 _no_negative_indexes()
2175 return list(iterable_query)[-1]
2176 else:
2177 return list(iterable_query[item : item + 1])[0]