Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/orm/util.py : 43%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# orm/util.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
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 MapperProperty # noqa
27from .interfaces import PropComparator # noqa
28from .path_registry import PathRegistry # noqa
29from .. import event
30from .. import exc as sa_exc
31from .. import inspection
32from .. import sql
33from .. import util
34from ..sql import expression
35from ..sql import util as sql_util
38all_cascades = frozenset(
39 (
40 "delete",
41 "delete-orphan",
42 "all",
43 "merge",
44 "expunge",
45 "save-update",
46 "refresh-expire",
47 "none",
48 )
49)
52class CascadeOptions(frozenset):
53 """Keeps track of the options sent to relationship().cascade"""
55 _add_w_all_cascades = all_cascades.difference(
56 ["all", "none", "delete-orphan"]
57 )
58 _allowed_cascades = all_cascades
60 _viewonly_cascades = ["expunge", "all", "none", "refresh-expire"]
62 __slots__ = (
63 "save_update",
64 "delete",
65 "refresh_expire",
66 "merge",
67 "expunge",
68 "delete_orphan",
69 )
71 def __new__(cls, value_list):
72 if isinstance(value_list, util.string_types) or value_list is None:
73 return cls.from_string(value_list)
74 values = set(value_list)
75 if values.difference(cls._allowed_cascades):
76 raise sa_exc.ArgumentError(
77 "Invalid cascade option(s): %s"
78 % ", ".join(
79 [
80 repr(x)
81 for x in sorted(
82 values.difference(cls._allowed_cascades)
83 )
84 ]
85 )
86 )
88 if "all" in values:
89 values.update(cls._add_w_all_cascades)
90 if "none" in values:
91 values.clear()
92 values.discard("all")
94 self = frozenset.__new__(CascadeOptions, values)
95 self.save_update = "save-update" in values
96 self.delete = "delete" in values
97 self.refresh_expire = "refresh-expire" in values
98 self.merge = "merge" in values
99 self.expunge = "expunge" in values
100 self.delete_orphan = "delete-orphan" in values
102 if self.delete_orphan and not self.delete:
103 util.warn(
104 "The 'delete-orphan' cascade " "option requires 'delete'."
105 )
106 return self
108 def __repr__(self):
109 return "CascadeOptions(%r)" % (",".join([x for x in sorted(self)]))
111 @classmethod
112 def from_string(cls, arg):
113 values = [c for c in re.split(r"\s*,\s*", arg or "") if c]
114 return cls(values)
117def _validator_events(desc, key, validator, include_removes, include_backrefs):
118 """Runs a validation method on an attribute value to be set or
119 appended.
120 """
122 if not include_backrefs:
124 def detect_is_backref(state, initiator):
125 impl = state.manager[key].impl
126 return initiator.impl is not impl
128 if include_removes:
130 def append(state, value, initiator):
131 if initiator.op is not attributes.OP_BULK_REPLACE and (
132 include_backrefs or not detect_is_backref(state, initiator)
133 ):
134 return validator(state.obj(), key, value, False)
135 else:
136 return value
138 def bulk_set(state, values, initiator):
139 if include_backrefs or not detect_is_backref(state, initiator):
140 obj = state.obj()
141 values[:] = [
142 validator(obj, key, value, False) for value in values
143 ]
145 def set_(state, value, oldvalue, initiator):
146 if include_backrefs or not detect_is_backref(state, initiator):
147 return validator(state.obj(), key, value, False)
148 else:
149 return value
151 def remove(state, value, initiator):
152 if include_backrefs or not detect_is_backref(state, initiator):
153 validator(state.obj(), key, value, True)
155 else:
157 def append(state, value, initiator):
158 if initiator.op is not attributes.OP_BULK_REPLACE and (
159 include_backrefs or not detect_is_backref(state, initiator)
160 ):
161 return validator(state.obj(), key, value)
162 else:
163 return value
165 def bulk_set(state, values, initiator):
166 if include_backrefs or not detect_is_backref(state, initiator):
167 obj = state.obj()
168 values[:] = [validator(obj, key, value) for value in values]
170 def set_(state, value, oldvalue, initiator):
171 if include_backrefs or not detect_is_backref(state, initiator):
172 return validator(state.obj(), key, value)
173 else:
174 return value
176 event.listen(desc, "append", append, raw=True, retval=True)
177 event.listen(desc, "bulk_replace", bulk_set, raw=True)
178 event.listen(desc, "set", set_, raw=True, retval=True)
179 if include_removes:
180 event.listen(desc, "remove", remove, raw=True, retval=True)
183def polymorphic_union(
184 table_map, typecolname, aliasname="p_union", cast_nulls=True
185):
186 """Create a ``UNION`` statement used by a polymorphic mapper.
188 See :ref:`concrete_inheritance` for an example of how
189 this is used.
191 :param table_map: mapping of polymorphic identities to
192 :class:`_schema.Table` objects.
193 :param typecolname: string name of a "discriminator" column, which will be
194 derived from the query, producing the polymorphic identity for
195 each row. If ``None``, no polymorphic discriminator is generated.
196 :param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
197 construct generated.
198 :param cast_nulls: if True, non-existent columns, which are represented
199 as labeled NULLs, will be passed into CAST. This is a legacy behavior
200 that is problematic on some backends such as Oracle - in which case it
201 can be set to False.
203 """
205 colnames = util.OrderedSet()
206 colnamemaps = {}
207 types = {}
208 for key in table_map:
209 table = table_map[key]
211 # mysql doesn't like selecting from a select;
212 # make it an alias of the select
213 if isinstance(table, sql.Select):
214 table = table.alias()
215 table_map[key] = table
217 m = {}
218 for c in table.c:
219 colnames.add(c.key)
220 m[c.key] = c
221 types[c.key] = c.type
222 colnamemaps[table] = m
224 def col(name, table):
225 try:
226 return colnamemaps[table][name]
227 except KeyError:
228 if cast_nulls:
229 return sql.cast(sql.null(), types[name]).label(name)
230 else:
231 return sql.type_coerce(sql.null(), types[name]).label(name)
233 result = []
234 for type_, table in table_map.items():
235 if typecolname is not None:
236 result.append(
237 sql.select(
238 [col(name, table) for name in colnames]
239 + [
240 sql.literal_column(
241 sql_util._quote_ddl_expr(type_)
242 ).label(typecolname)
243 ],
244 from_obj=[table],
245 )
246 )
247 else:
248 result.append(
249 sql.select(
250 [col(name, table) for name in colnames], from_obj=[table]
251 )
252 )
253 return sql.union_all(*result).alias(aliasname)
256def identity_key(*args, **kwargs):
257 """Generate "identity key" tuples, as are used as keys in the
258 :attr:`.Session.identity_map` dictionary.
260 This function has several call styles:
262 * ``identity_key(class, ident, identity_token=token)``
264 This form receives a mapped class and a primary key scalar or
265 tuple as an argument.
267 E.g.::
269 >>> identity_key(MyClass, (1, 2))
270 (<class '__main__.MyClass'>, (1, 2), None)
272 :param class: mapped class (must be a positional argument)
273 :param ident: primary key, may be a scalar or tuple argument.
274 :param identity_token: optional identity token
276 .. versionadded:: 1.2 added identity_token
279 * ``identity_key(instance=instance)``
281 This form will produce the identity key for a given instance. The
282 instance need not be persistent, only that its primary key attributes
283 are populated (else the key will contain ``None`` for those missing
284 values).
286 E.g.::
288 >>> instance = MyClass(1, 2)
289 >>> identity_key(instance=instance)
290 (<class '__main__.MyClass'>, (1, 2), None)
292 In this form, the given instance is ultimately run though
293 :meth:`_orm.Mapper.identity_key_from_instance`, which will have the
294 effect of performing a database check for the corresponding row
295 if the object is expired.
297 :param instance: object instance (must be given as a keyword arg)
299 * ``identity_key(class, row=row, identity_token=token)``
301 This form is similar to the class/tuple form, except is passed a
302 database result row as a :class:`.RowProxy` object.
304 E.g.::
306 >>> row = engine.execute("select * from table where a=1 and b=2").\
307first()
308 >>> identity_key(MyClass, row=row)
309 (<class '__main__.MyClass'>, (1, 2), None)
311 :param class: mapped class (must be a positional argument)
312 :param row: :class:`.RowProxy` row returned by a
313 :class:`_engine.ResultProxy`
314 (must be given as a keyword arg)
315 :param identity_token: optional identity token
317 .. versionadded:: 1.2 added identity_token
319 """
320 if args:
321 row = None
322 largs = len(args)
323 if largs == 1:
324 class_ = args[0]
325 try:
326 row = kwargs.pop("row")
327 except KeyError:
328 ident = kwargs.pop("ident")
329 elif largs in (2, 3):
330 class_, ident = args
331 else:
332 raise sa_exc.ArgumentError(
333 "expected up to three positional arguments, " "got %s" % largs
334 )
336 identity_token = kwargs.pop("identity_token", None)
337 if kwargs:
338 raise sa_exc.ArgumentError(
339 "unknown keyword arguments: %s" % ", ".join(kwargs)
340 )
341 mapper = class_mapper(class_)
342 if row is None:
343 return mapper.identity_key_from_primary_key(
344 util.to_list(ident), identity_token=identity_token
345 )
346 else:
347 return mapper.identity_key_from_row(
348 row, identity_token=identity_token
349 )
350 else:
351 instance = kwargs.pop("instance")
352 if kwargs:
353 raise sa_exc.ArgumentError(
354 "unknown keyword arguments: %s" % ", ".join(kwargs.keys)
355 )
356 mapper = object_mapper(instance)
357 return mapper.identity_key_from_instance(instance)
360class ORMAdapter(sql_util.ColumnAdapter):
361 """ColumnAdapter subclass which excludes adaptation of entities from
362 non-matching mappers.
364 """
366 def __init__(
367 self,
368 entity,
369 equivalents=None,
370 adapt_required=False,
371 allow_label_resolve=True,
372 anonymize_labels=False,
373 ):
374 info = inspection.inspect(entity)
376 self.mapper = info.mapper
377 selectable = info.selectable
378 is_aliased_class = info.is_aliased_class
379 if is_aliased_class:
380 self.aliased_class = entity
381 else:
382 self.aliased_class = None
384 sql_util.ColumnAdapter.__init__(
385 self,
386 selectable,
387 equivalents,
388 adapt_required=adapt_required,
389 allow_label_resolve=allow_label_resolve,
390 anonymize_labels=anonymize_labels,
391 include_fn=self._include_fn,
392 )
394 def _include_fn(self, elem):
395 entity = elem._annotations.get("parentmapper", None)
396 return not entity or entity.isa(self.mapper)
399class AliasedClass(object):
400 r"""Represents an "aliased" form of a mapped class for usage with Query.
402 The ORM equivalent of a :func:`~sqlalchemy.sql.expression.alias`
403 construct, this object mimics the mapped class using a
404 ``__getattr__`` scheme and maintains a reference to a
405 real :class:`~sqlalchemy.sql.expression.Alias` object.
407 A primary purpose of :class:`.AliasedClass` is to serve as an alternate
408 within a SQL statement generated by the ORM, such that an existing
409 mapped entity can be used in multiple contexts. A simple example::
411 # find all pairs of users with the same name
412 user_alias = aliased(User)
413 session.query(User, user_alias).\
414 join((user_alias, User.id > user_alias.id)).\
415 filter(User.name == user_alias.name)
417 :class:`.AliasedClass` is also capable of mapping an existing mapped
418 class to an entirely new selectable, provided this selectable is column-
419 compatible with the existing mapped selectable, and it can also be
420 configured in a mapping as the target of a :func:`_orm.relationship`.
421 See the links below for examples.
423 The :class:`.AliasedClass` object is constructed typically using the
424 :func:`_orm.aliased` function. It also is produced with additional
425 configuration when using the :func:`_orm.with_polymorphic` function.
427 The resulting object is an instance of :class:`.AliasedClass`.
428 This object implements an attribute scheme which produces the
429 same attribute and method interface as the original mapped
430 class, allowing :class:`.AliasedClass` to be compatible
431 with any attribute technique which works on the original class,
432 including hybrid attributes (see :ref:`hybrids_toplevel`).
434 The :class:`.AliasedClass` can be inspected for its underlying
435 :class:`_orm.Mapper`, aliased selectable, and other information
436 using :func:`_sa.inspect`::
438 from sqlalchemy import inspect
439 my_alias = aliased(MyClass)
440 insp = inspect(my_alias)
442 The resulting inspection object is an instance of :class:`.AliasedInsp`.
445 .. seealso::
447 :func:`.aliased`
449 :func:`.with_polymorphic`
451 :ref:`relationship_aliased_class`
453 :ref:`relationship_to_window_function`
456 """
458 def __init__(
459 self,
460 cls,
461 alias=None,
462 name=None,
463 flat=False,
464 adapt_on_names=False,
465 # TODO: None for default here?
466 with_polymorphic_mappers=(),
467 with_polymorphic_discriminator=None,
468 base_alias=None,
469 use_mapper_path=False,
470 represents_outer_join=False,
471 ):
472 mapper = _class_to_mapper(cls)
473 if alias is None:
474 alias = mapper._with_polymorphic_selectable.alias(
475 name=name, flat=flat
476 )
478 self._aliased_insp = AliasedInsp(
479 self,
480 mapper,
481 alias,
482 name,
483 with_polymorphic_mappers
484 if with_polymorphic_mappers
485 else mapper.with_polymorphic_mappers,
486 with_polymorphic_discriminator
487 if with_polymorphic_discriminator is not None
488 else mapper.polymorphic_on,
489 base_alias,
490 use_mapper_path,
491 adapt_on_names,
492 represents_outer_join,
493 )
495 self.__name__ = "AliasedClass_%s" % mapper.class_.__name__
497 def __getattr__(self, key):
498 try:
499 _aliased_insp = self.__dict__["_aliased_insp"]
500 except KeyError:
501 raise AttributeError()
502 else:
503 target = _aliased_insp._target
504 # maintain all getattr mechanics
505 attr = getattr(target, key)
507 # attribute is a method, that will be invoked against a
508 # "self"; so just return a new method with the same function and
509 # new self
510 if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
511 return types.MethodType(attr.__func__, self)
513 # attribute is a descriptor, that will be invoked against a
514 # "self"; so invoke the descriptor against this self
515 if hasattr(attr, "__get__"):
516 attr = attr.__get__(None, self)
518 # attributes within the QueryableAttribute system will want this
519 # to be invoked so the object can be adapted
520 if hasattr(attr, "adapt_to_entity"):
521 attr = attr.adapt_to_entity(_aliased_insp)
522 setattr(self, key, attr)
524 return attr
526 def __repr__(self):
527 return "<AliasedClass at 0x%x; %s>" % (
528 id(self),
529 self._aliased_insp._target.__name__,
530 )
532 def __str__(self):
533 return str(self._aliased_insp)
536class AliasedInsp(InspectionAttr):
537 """Provide an inspection interface for an
538 :class:`.AliasedClass` object.
540 The :class:`.AliasedInsp` object is returned
541 given an :class:`.AliasedClass` using the
542 :func:`_sa.inspect` function::
544 from sqlalchemy import inspect
545 from sqlalchemy.orm import aliased
547 my_alias = aliased(MyMappedClass)
548 insp = inspect(my_alias)
550 Attributes on :class:`.AliasedInsp`
551 include:
553 * ``entity`` - the :class:`.AliasedClass` represented.
554 * ``mapper`` - the :class:`_orm.Mapper` mapping the underlying class.
555 * ``selectable`` - the :class:`_expression.Alias`
556 construct which ultimately
557 represents an aliased :class:`_schema.Table` or
558 :class:`_expression.Select`
559 construct.
560 * ``name`` - the name of the alias. Also is used as the attribute
561 name when returned in a result tuple from :class:`_query.Query`.
562 * ``with_polymorphic_mappers`` - collection of :class:`_orm.Mapper`
563 objects
564 indicating all those mappers expressed in the select construct
565 for the :class:`.AliasedClass`.
566 * ``polymorphic_on`` - an alternate column or SQL expression which
567 will be used as the "discriminator" for a polymorphic load.
569 .. seealso::
571 :ref:`inspection_toplevel`
573 """
575 def __init__(
576 self,
577 entity,
578 mapper,
579 selectable,
580 name,
581 with_polymorphic_mappers,
582 polymorphic_on,
583 _base_alias,
584 _use_mapper_path,
585 adapt_on_names,
586 represents_outer_join,
587 ):
588 self._weak_entity = weakref.ref(entity)
589 self.mapper = mapper
590 self.selectable = (
591 self.persist_selectable
592 ) = self.local_table = selectable
593 self.name = name
594 self.polymorphic_on = polymorphic_on
595 self._base_alias = weakref.ref(_base_alias or self)
596 self._use_mapper_path = _use_mapper_path
597 self.represents_outer_join = represents_outer_join
599 if with_polymorphic_mappers:
600 self._is_with_polymorphic = True
601 self.with_polymorphic_mappers = with_polymorphic_mappers
602 self._with_polymorphic_entities = []
603 for poly in self.with_polymorphic_mappers:
604 if poly is not mapper:
605 ent = AliasedClass(
606 poly.class_,
607 selectable,
608 base_alias=self,
609 adapt_on_names=adapt_on_names,
610 use_mapper_path=_use_mapper_path,
611 )
613 setattr(self.entity, poly.class_.__name__, ent)
614 self._with_polymorphic_entities.append(ent._aliased_insp)
616 else:
617 self._is_with_polymorphic = False
618 self.with_polymorphic_mappers = [mapper]
620 self._adapter = sql_util.ColumnAdapter(
621 selectable,
622 equivalents=mapper._equivalent_columns,
623 adapt_on_names=adapt_on_names,
624 anonymize_labels=True,
625 )
627 self._adapt_on_names = adapt_on_names
628 self._target = mapper.class_
630 @property
631 def entity(self):
632 return self._weak_entity()
634 is_aliased_class = True
635 "always returns True"
637 @property
638 def class_(self):
639 """Return the mapped class ultimately represented by this
640 :class:`.AliasedInsp`."""
641 return self.mapper.class_
643 @property
644 def _path_registry(self):
645 if self._use_mapper_path:
646 return self.mapper._path_registry
647 else:
648 return PathRegistry.per_mapper(self)
650 def __getstate__(self):
651 return {
652 "entity": self.entity,
653 "mapper": self.mapper,
654 "alias": self.selectable,
655 "name": self.name,
656 "adapt_on_names": self._adapt_on_names,
657 "with_polymorphic_mappers": self.with_polymorphic_mappers,
658 "with_polymorphic_discriminator": self.polymorphic_on,
659 "base_alias": self._base_alias(),
660 "use_mapper_path": self._use_mapper_path,
661 "represents_outer_join": self.represents_outer_join,
662 }
664 def __setstate__(self, state):
665 self.__init__(
666 state["entity"],
667 state["mapper"],
668 state["alias"],
669 state["name"],
670 state["with_polymorphic_mappers"],
671 state["with_polymorphic_discriminator"],
672 state["base_alias"],
673 state["use_mapper_path"],
674 state["adapt_on_names"],
675 state["represents_outer_join"],
676 )
678 def _adapt_element(self, elem):
679 return self._adapter.traverse(elem)._annotate(
680 {"parententity": self, "parentmapper": self.mapper}
681 )
683 def _entity_for_mapper(self, mapper):
684 self_poly = self.with_polymorphic_mappers
685 if mapper in self_poly:
686 if mapper is self.mapper:
687 return self
688 else:
689 return getattr(
690 self.entity, mapper.class_.__name__
691 )._aliased_insp
692 elif mapper.isa(self.mapper):
693 return self
694 else:
695 assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
697 @util.memoized_property
698 def _get_clause(self):
699 onclause, replacemap = self.mapper._get_clause
700 return (
701 self._adapter.traverse(onclause),
702 {
703 self._adapter.traverse(col): param
704 for col, param in replacemap.items()
705 },
706 )
708 @util.memoized_property
709 def _memoized_values(self):
710 return {}
712 def _memo(self, key, callable_, *args, **kw):
713 if key in self._memoized_values:
714 return self._memoized_values[key]
715 else:
716 self._memoized_values[key] = value = callable_(*args, **kw)
717 return value
719 def __repr__(self):
720 if self.with_polymorphic_mappers:
721 with_poly = "(%s)" % ", ".join(
722 mp.class_.__name__ for mp in self.with_polymorphic_mappers
723 )
724 else:
725 with_poly = ""
726 return "<AliasedInsp at 0x%x; %s%s>" % (
727 id(self),
728 self.class_.__name__,
729 with_poly,
730 )
732 def __str__(self):
733 if self._is_with_polymorphic:
734 return "with_polymorphic(%s, [%s])" % (
735 self._target.__name__,
736 ", ".join(
737 mp.class_.__name__
738 for mp in self.with_polymorphic_mappers
739 if mp is not self.mapper
740 ),
741 )
742 else:
743 return "aliased(%s)" % (self._target.__name__,)
746inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
747inspection._inspects(AliasedInsp)(lambda target: target)
750def aliased(element, alias=None, name=None, flat=False, adapt_on_names=False):
751 """Produce an alias of the given element, usually an :class:`.AliasedClass`
752 instance.
754 E.g.::
756 my_alias = aliased(MyClass)
758 session.query(MyClass, my_alias).filter(MyClass.id > my_alias.id)
760 The :func:`.aliased` function is used to create an ad-hoc mapping
761 of a mapped class to a new selectable. By default, a selectable
762 is generated from the normally mapped selectable (typically a
763 :class:`_schema.Table`) using the :meth:`_expression.FromClause.alias`
764 method.
765 However, :func:`.aliased` can also be used to link the class to
766 a new :func:`_expression.select` statement. Also, the
767 :func:`.with_polymorphic`
768 function is a variant of :func:`.aliased` that is intended to specify
769 a so-called "polymorphic selectable", that corresponds to the union
770 of several joined-inheritance subclasses at once.
772 For convenience, the :func:`.aliased` function also accepts plain
773 :class:`_expression.FromClause` constructs, such as a
774 :class:`_schema.Table` or
775 :func:`_expression.select` construct. In those cases, the
776 :meth:`_expression.FromClause.alias`
777 method is called on the object and the new :class:`_expression.Alias`
778 object
779 returned. The returned :class:`_expression.Alias`
780 is not ORM-mapped in this case.
782 :param element: element to be aliased. Is normally a mapped class,
783 but for convenience can also be a :class:`_expression.FromClause` element
784 .
785 :param alias: Optional selectable unit to map the element to. This is
786 usually used to link the object to a subquery, and should be an aliased
787 select construct as one would produce from the
788 :meth:`_query.Query.subquery` method or
789 the :meth:`_expression.Select.alias` methods of the
790 :func:`_expression.select` construct.
792 :param name: optional string name to use for the alias, if not specified
793 by the ``alias`` parameter. The name, among other things, forms the
794 attribute name that will be accessible via tuples returned by a
795 :class:`_query.Query` object.
797 :param flat: Boolean, will be passed through to the
798 :meth:`_expression.FromClause.alias` call so that aliases of
799 :class:`_expression.Join` objects
800 don't include an enclosing SELECT. This can lead to more efficient
801 queries in many circumstances. A JOIN against a nested JOIN will be
802 rewritten as a JOIN against an aliased SELECT subquery on backends that
803 don't support this syntax.
805 .. seealso:: :meth:`_expression.Join.alias`
807 :param adapt_on_names: if True, more liberal "matching" will be used when
808 mapping the mapped columns of the ORM entity to those of the
809 given selectable - a name-based match will be performed if the
810 given selectable doesn't otherwise have a column that corresponds
811 to one on the entity. The use case for this is when associating
812 an entity with some derived selectable such as one that uses
813 aggregate functions::
815 class UnitPrice(Base):
816 __tablename__ = 'unit_price'
817 ...
818 unit_id = Column(Integer)
819 price = Column(Numeric)
821 aggregated_unit_price = Session.query(
822 func.sum(UnitPrice.price).label('price')
823 ).group_by(UnitPrice.unit_id).subquery()
825 aggregated_unit_price = aliased(UnitPrice,
826 alias=aggregated_unit_price, adapt_on_names=True)
828 Above, functions on ``aggregated_unit_price`` which refer to
829 ``.price`` will return the
830 ``func.sum(UnitPrice.price).label('price')`` column, as it is
831 matched on the name "price". Ordinarily, the "price" function
832 wouldn't have any "column correspondence" to the actual
833 ``UnitPrice.price`` column as it is not a proxy of the original.
835 """
836 if isinstance(element, expression.FromClause):
837 if adapt_on_names:
838 raise sa_exc.ArgumentError(
839 "adapt_on_names only applies to ORM elements"
840 )
841 return element.alias(name, flat=flat)
842 else:
843 return AliasedClass(
844 element,
845 alias=alias,
846 flat=flat,
847 name=name,
848 adapt_on_names=adapt_on_names,
849 )
852def with_polymorphic(
853 base,
854 classes,
855 selectable=False,
856 flat=False,
857 polymorphic_on=None,
858 aliased=False,
859 innerjoin=False,
860 _use_mapper_path=False,
861 _existing_alias=None,
862):
863 """Produce an :class:`.AliasedClass` construct which specifies
864 columns for descendant mappers of the given base.
866 Using this method will ensure that each descendant mapper's
867 tables are included in the FROM clause, and will allow filter()
868 criterion to be used against those tables. The resulting
869 instances will also have those columns already loaded so that
870 no "post fetch" of those columns will be required.
872 .. seealso::
874 :ref:`with_polymorphic` - full discussion of
875 :func:`_orm.with_polymorphic`.
877 :param base: Base class to be aliased.
879 :param classes: a single class or mapper, or list of
880 class/mappers, which inherit from the base class.
881 Alternatively, it may also be the string ``'*'``, in which case
882 all descending mapped classes will be added to the FROM clause.
884 :param aliased: when True, the selectable will be wrapped in an
885 alias, that is ``(SELECT * FROM <fromclauses>) AS anon_1``.
886 This can be important when using the with_polymorphic()
887 to create the target of a JOIN on a backend that does not
888 support parenthesized joins, such as SQLite and older
889 versions of MySQL. However if the
890 :paramref:`.with_polymorphic.selectable` parameter is in use
891 with an existing :class:`_expression.Alias` construct,
892 then you should not
893 set this flag.
895 :param flat: Boolean, will be passed through to the
896 :meth:`_expression.FromClause.alias` call so that aliases of
897 :class:`_expression.Join`
898 objects don't include an enclosing SELECT. This can lead to more
899 efficient queries in many circumstances. A JOIN against a nested JOIN
900 will be rewritten as a JOIN against an aliased SELECT subquery on
901 backends that don't support this syntax.
903 Setting ``flat`` to ``True`` implies the ``aliased`` flag is
904 also ``True``.
906 .. versionadded:: 0.9.0
908 .. seealso:: :meth:`_expression.Join.alias`
910 :param selectable: a table or select() statement that will
911 be used in place of the generated FROM clause. This argument is
912 required if any of the desired classes use concrete table
913 inheritance, since SQLAlchemy currently cannot generate UNIONs
914 among tables automatically. If used, the ``selectable`` argument
915 must represent the full set of tables and columns mapped by every
916 mapped class. Otherwise, the unaccounted mapped columns will
917 result in their table being appended directly to the FROM clause
918 which will usually lead to incorrect results.
920 :param polymorphic_on: a column to be used as the "discriminator"
921 column for the given selectable. If not given, the polymorphic_on
922 attribute of the base classes' mapper will be used, if any. This
923 is useful for mappings that don't have polymorphic loading
924 behavior by default.
926 :param innerjoin: if True, an INNER JOIN will be used. This should
927 only be specified if querying for one specific subtype only
928 """
929 primary_mapper = _class_to_mapper(base)
930 if _existing_alias:
931 assert _existing_alias.mapper is primary_mapper
932 classes = util.to_set(classes)
933 new_classes = set(
934 [mp.class_ for mp in _existing_alias.with_polymorphic_mappers]
935 )
936 if classes == new_classes:
937 return _existing_alias
938 else:
939 classes = classes.union(new_classes)
940 mappers, selectable = primary_mapper._with_polymorphic_args(
941 classes, selectable, innerjoin=innerjoin
942 )
943 if aliased or flat:
944 selectable = selectable.alias(flat=flat)
945 return AliasedClass(
946 base,
947 selectable,
948 with_polymorphic_mappers=mappers,
949 with_polymorphic_discriminator=polymorphic_on,
950 use_mapper_path=_use_mapper_path,
951 represents_outer_join=not innerjoin,
952 )
955def _orm_annotate(element, exclude=None):
956 """Deep copy the given ClauseElement, annotating each element with the
957 "_orm_adapt" flag.
959 Elements within the exclude collection will be cloned but not annotated.
961 """
962 return sql_util._deep_annotate(element, {"_orm_adapt": True}, exclude)
965def _orm_deannotate(element):
966 """Remove annotations that link a column to a particular mapping.
968 Note this doesn't affect "remote" and "foreign" annotations
969 passed by the :func:`_orm.foreign` and :func:`_orm.remote`
970 annotators.
972 """
974 return sql_util._deep_deannotate(
975 element, values=("_orm_adapt", "parententity")
976 )
979def _orm_full_deannotate(element):
980 return sql_util._deep_deannotate(element)
983class _ORMJoin(expression.Join):
984 """Extend Join to support ORM constructs as input."""
986 __visit_name__ = expression.Join.__visit_name__
988 def __init__(
989 self,
990 left,
991 right,
992 onclause=None,
993 isouter=False,
994 full=False,
995 _left_memo=None,
996 _right_memo=None,
997 ):
998 left_info = inspection.inspect(left)
999 left_orm_info = getattr(left, "_joined_from_info", left_info)
1001 right_info = inspection.inspect(right)
1002 adapt_to = right_info.selectable
1004 self._joined_from_info = right_info
1006 self._left_memo = _left_memo
1007 self._right_memo = _right_memo
1009 if isinstance(onclause, util.string_types):
1010 onclause = getattr(left_orm_info.entity, onclause)
1012 if isinstance(onclause, attributes.QueryableAttribute):
1013 on_selectable = onclause.comparator._source_selectable()
1014 prop = onclause.property
1015 elif isinstance(onclause, MapperProperty):
1016 prop = onclause
1017 on_selectable = prop.parent.selectable
1018 else:
1019 prop = None
1021 if prop:
1022 if sql_util.clause_is_present(on_selectable, left_info.selectable):
1023 adapt_from = on_selectable
1024 else:
1025 adapt_from = left_info.selectable
1027 (
1028 pj,
1029 sj,
1030 source,
1031 dest,
1032 secondary,
1033 target_adapter,
1034 ) = prop._create_joins(
1035 source_selectable=adapt_from,
1036 dest_selectable=adapt_to,
1037 source_polymorphic=True,
1038 dest_polymorphic=True,
1039 of_type_mapper=right_info.mapper,
1040 alias_secondary=True,
1041 )
1043 if sj is not None:
1044 if isouter:
1045 # note this is an inner join from secondary->right
1046 right = sql.join(secondary, right, sj)
1047 onclause = pj
1048 else:
1049 left = sql.join(left, secondary, pj, isouter)
1050 onclause = sj
1051 else:
1052 onclause = pj
1053 self._target_adapter = target_adapter
1055 expression.Join.__init__(self, left, right, onclause, isouter, full)
1057 if (
1058 not prop
1059 and getattr(right_info, "mapper", None)
1060 and right_info.mapper.single
1061 ):
1062 # if single inheritance target and we are using a manual
1063 # or implicit ON clause, augment it the same way we'd augment the
1064 # WHERE.
1065 single_crit = right_info.mapper._single_table_criterion
1066 if single_crit is not None:
1067 if right_info.is_aliased_class:
1068 single_crit = right_info._adapter.traverse(single_crit)
1069 self.onclause = self.onclause & single_crit
1071 def _splice_into_center(self, other):
1072 """Splice a join into the center.
1074 Given join(a, b) and join(b, c), return join(a, b).join(c)
1076 """
1077 leftmost = other
1078 while isinstance(leftmost, sql.Join):
1079 leftmost = leftmost.left
1081 assert self.right is leftmost
1083 left = _ORMJoin(
1084 self.left,
1085 other.left,
1086 self.onclause,
1087 isouter=self.isouter,
1088 _left_memo=self._left_memo,
1089 _right_memo=other._left_memo,
1090 )
1092 return _ORMJoin(
1093 left,
1094 other.right,
1095 other.onclause,
1096 isouter=other.isouter,
1097 _right_memo=other._right_memo,
1098 )
1100 def join(
1101 self,
1102 right,
1103 onclause=None,
1104 isouter=False,
1105 full=False,
1106 join_to_left=None,
1107 ):
1108 return _ORMJoin(self, right, onclause, full=full, isouter=isouter)
1110 def outerjoin(self, right, onclause=None, full=False, join_to_left=None):
1111 return _ORMJoin(self, right, onclause, isouter=True, full=full)
1114def join(
1115 left, right, onclause=None, isouter=False, full=False, join_to_left=None
1116):
1117 r"""Produce an inner join between left and right clauses.
1119 :func:`_orm.join` is an extension to the core join interface
1120 provided by :func:`_expression.join()`, where the
1121 left and right selectables may be not only core selectable
1122 objects such as :class:`_schema.Table`, but also mapped classes or
1123 :class:`.AliasedClass` instances. The "on" clause can
1124 be a SQL expression, or an attribute or string name
1125 referencing a configured :func:`_orm.relationship`.
1127 :func:`_orm.join` is not commonly needed in modern usage,
1128 as its functionality is encapsulated within that of the
1129 :meth:`_query.Query.join` method, which features a
1130 significant amount of automation beyond :func:`_orm.join`
1131 by itself. Explicit usage of :func:`_orm.join`
1132 with :class:`_query.Query` involves usage of the
1133 :meth:`_query.Query.select_from` method, as in::
1135 from sqlalchemy.orm import join
1136 session.query(User).\
1137 select_from(join(User, Address, User.addresses)).\
1138 filter(Address.email_address=='foo@bar.com')
1140 In modern SQLAlchemy the above join can be written more
1141 succinctly as::
1143 session.query(User).\
1144 join(User.addresses).\
1145 filter(Address.email_address=='foo@bar.com')
1147 See :meth:`_query.Query.join` for information on modern usage
1148 of ORM level joins.
1150 .. deprecated:: 0.8
1152 the ``join_to_left`` parameter is deprecated, and will be removed
1153 in a future release. The parameter has no effect.
1155 """
1156 return _ORMJoin(left, right, onclause, isouter, full)
1159def outerjoin(left, right, onclause=None, full=False, join_to_left=None):
1160 """Produce a left outer join between left and right clauses.
1162 This is the "outer join" version of the :func:`_orm.join` function,
1163 featuring the same behavior except that an OUTER JOIN is generated.
1164 See that function's documentation for other usage details.
1166 """
1167 return _ORMJoin(left, right, onclause, True, full)
1170def with_parent(instance, prop, from_entity=None):
1171 """Create filtering criterion that relates this query's primary entity
1172 to the given related instance, using established
1173 :func:`_orm.relationship()`
1174 configuration.
1176 The SQL rendered is the same as that rendered when a lazy loader
1177 would fire off from the given parent on that attribute, meaning
1178 that the appropriate state is taken from the parent object in
1179 Python without the need to render joins to the parent table
1180 in the rendered statement.
1182 :param instance:
1183 An instance which has some :func:`_orm.relationship`.
1185 :param property:
1186 String property name, or class-bound attribute, which indicates
1187 what relationship from the instance should be used to reconcile the
1188 parent/child relationship.
1190 :param from_entity:
1191 Entity in which to consider as the left side. This defaults to the
1192 "zero" entity of the :class:`_query.Query` itself.
1194 .. versionadded:: 1.2
1196 """
1197 if isinstance(prop, util.string_types):
1198 mapper = object_mapper(instance)
1199 prop = getattr(mapper.class_, prop).property
1200 elif isinstance(prop, attributes.QueryableAttribute):
1201 prop = prop.property
1203 return prop._with_parent(instance, from_entity=from_entity)
1206def has_identity(object_):
1207 """Return True if the given object has a database
1208 identity.
1210 This typically corresponds to the object being
1211 in either the persistent or detached state.
1213 .. seealso::
1215 :func:`.was_deleted`
1217 """
1218 state = attributes.instance_state(object_)
1219 return state.has_identity
1222def was_deleted(object_):
1223 """Return True if the given object was deleted
1224 within a session flush.
1226 This is regardless of whether or not the object is
1227 persistent or detached.
1229 .. seealso::
1231 :attr:`.InstanceState.was_deleted`
1233 """
1235 state = attributes.instance_state(object_)
1236 return state.was_deleted
1239def _entity_corresponds_to(given, entity):
1240 """determine if 'given' corresponds to 'entity', in terms
1241 of an entity passed to Query that would match the same entity
1242 being referred to elsewhere in the query.
1244 """
1245 if entity.is_aliased_class:
1246 if given.is_aliased_class:
1247 if entity._base_alias() is given._base_alias():
1248 return True
1249 return False
1250 elif given.is_aliased_class:
1251 if given._use_mapper_path:
1252 return entity in given.with_polymorphic_mappers
1253 else:
1254 return entity is given
1256 return entity.common_parent(given)
1259def _entity_corresponds_to_use_path_impl(given, entity):
1260 """determine if 'given' corresponds to 'entity', in terms
1261 of a path of loader options where a mapped attribute is taken to
1262 be a member of a parent entity.
1264 e.g.::
1266 someoption(A).someoption(A.b) # -> fn(A, A) -> True
1267 someoption(A).someoption(C.d) # -> fn(A, C) -> False
1269 a1 = aliased(A)
1270 someoption(a1).someoption(A.b) # -> fn(a1, A) -> False
1271 someoption(a1).someoption(a1.b) # -> fn(a1, a1) -> True
1273 wp = with_polymorphic(A, [A1, A2])
1274 someoption(wp).someoption(A1.foo) # -> fn(wp, A1) -> False
1275 someoption(wp).someoption(wp.A1.foo) # -> fn(wp, wp.A1) -> True
1278 """
1279 if given.is_aliased_class:
1280 return (
1281 entity.is_aliased_class
1282 and not entity._use_mapper_path
1283 and (given is entity or given in entity._with_polymorphic_entities)
1284 )
1285 elif not entity.is_aliased_class:
1286 return given.common_parent(entity.mapper)
1287 else:
1288 return (
1289 entity._use_mapper_path
1290 and given in entity.with_polymorphic_mappers
1291 )
1294def _entity_isa(given, mapper):
1295 """determine if 'given' "is a" mapper, in terms of the given
1296 would load rows of type 'mapper'.
1298 """
1299 if given.is_aliased_class:
1300 return mapper in given.with_polymorphic_mappers or given.mapper.isa(
1301 mapper
1302 )
1303 elif given.with_polymorphic_mappers:
1304 return mapper in given.with_polymorphic_mappers
1305 else:
1306 return given.isa(mapper)
1309def randomize_unitofwork():
1310 """Use random-ordering sets within the unit of work in order
1311 to detect unit of work sorting issues.
1313 This is a utility function that can be used to help reproduce
1314 inconsistent unit of work sorting issues. For example,
1315 if two kinds of objects A and B are being inserted, and
1316 B has a foreign key reference to A - the A must be inserted first.
1317 However, if there is no relationship between A and B, the unit of work
1318 won't know to perform this sorting, and an operation may or may not
1319 fail, depending on how the ordering works out. Since Python sets
1320 and dictionaries have non-deterministic ordering, such an issue may
1321 occur on some runs and not on others, and in practice it tends to
1322 have a great dependence on the state of the interpreter. This leads
1323 to so-called "heisenbugs" where changing entirely irrelevant aspects
1324 of the test program still cause the failure behavior to change.
1326 By calling ``randomize_unitofwork()`` when a script first runs, the
1327 ordering of a key series of sets within the unit of work implementation
1328 are randomized, so that the script can be minimized down to the
1329 fundamental mapping and operation that's failing, while still reproducing
1330 the issue on at least some runs.
1332 This utility is also available when running the test suite via the
1333 ``--reversetop`` flag.
1335 """
1336 from sqlalchemy.orm import unitofwork, session, mapper, dependency
1337 from sqlalchemy.util import topological
1338 from sqlalchemy.testing.util import RandomSet
1340 topological.set = (
1341 unitofwork.set
1342 ) = session.set = mapper.set = dependency.set = RandomSet