1# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors 2# <see AUTHORS file> 3# 4# This module is part of SQLAlchemy and is released under 5# the MIT License: http://www.opensource.org/licenses/mit-license.php 6 7""" 8 9""" 10 11from .interfaces import MapperOption, PropComparator 12from .. import util 13from ..sql.base import _generative, Generative 14from .. import exc as sa_exc, inspect 15from .base import _is_aliased_class, _class_to_mapper 16from . import util as orm_util 17from .path_registry import PathRegistry, TokenRegistry, \ 18 _WILDCARD_TOKEN, _DEFAULT_TOKEN 19 20 21class Load(Generative, MapperOption): 22 """Represents loader options which modify the state of a 23 :class:`.Query` in order to affect how various mapped attributes are 24 loaded. 25 26 .. versionadded:: 0.9.0 The :meth:`.Load` system is a new foundation for 27 the existing system of loader options, including options such as 28 :func:`.orm.joinedload`, :func:`.orm.defer`, and others. In 29 particular, it introduces a new method-chained system that replaces the 30 need for dot-separated paths as well as "_all()" options such as 31 :func:`.orm.joinedload_all`. 32 33 A :class:`.Load` object can be used directly or indirectly. To use one 34 directly, instantiate given the parent class. This style of usage is 35 useful when dealing with a :class:`.Query` that has multiple entities, 36 or when producing a loader option that can be applied generically to 37 any style of query:: 38 39 myopt = Load(MyClass).joinedload("widgets") 40 41 The above ``myopt`` can now be used with :meth:`.Query.options`:: 42 43 session.query(MyClass).options(myopt) 44 45 The :class:`.Load` construct is invoked indirectly whenever one makes use 46 of the various loader options that are present in ``sqlalchemy.orm``, 47 including options such as :func:`.orm.joinedload`, :func:`.orm.defer`, 48 :func:`.orm.subqueryload`, and all the rest. These constructs produce an 49 "anonymous" form of the :class:`.Load` object which tracks attributes and 50 options, but is not linked to a parent class until it is associated with a 51 parent :class:`.Query`:: 52 53 # produce "unbound" Load object 54 myopt = joinedload("widgets") 55 56 # when applied using options(), the option is "bound" to the 57 # class observed in the given query, e.g. MyClass 58 session.query(MyClass).options(myopt) 59 60 Whether the direct or indirect style is used, the :class:`.Load` object 61 returned now represents a specific "path" along the entities of a 62 :class:`.Query`. This path can be traversed using a standard 63 method-chaining approach. Supposing a class hierarchy such as ``User``, 64 ``User.addresses -> Address``, ``User.orders -> Order`` and 65 ``Order.items -> Item``, we can specify a variety of loader options along 66 each element in the "path":: 67 68 session.query(User).options( 69 joinedload("addresses"), 70 subqueryload("orders").joinedload("items") 71 ) 72 73 Where above, the ``addresses`` collection will be joined-loaded, the 74 ``orders`` collection will be subquery-loaded, and within that subquery 75 load the ``items`` collection will be joined-loaded. 76 77 78 """ 79 80 def __init__(self, entity): 81 insp = inspect(entity) 82 self.path = insp._path_registry 83 self.context = {} 84 self.local_opts = {} 85 86 def _generate(self): 87 cloned = super(Load, self)._generate() 88 cloned.local_opts = {} 89 return cloned 90 91 _merge_into_path = False 92 strategy = None 93 propagate_to_loaders = False 94 95 def process_query(self, query): 96 self._process(query, True) 97 98 def process_query_conditionally(self, query): 99 self._process(query, False) 100 101 def _process(self, query, raiseerr): 102 current_path = query._current_path 103 if current_path: 104 for (token, start_path), loader in self.context.items(): 105 chopped_start_path = self._chop_path(start_path, current_path) 106 if chopped_start_path is not None: 107 query._attributes[(token, chopped_start_path)] = loader 108 else: 109 query._attributes.update(self.context) 110 111 def _generate_path(self, path, attr, wildcard_key, raiseerr=True): 112 if raiseerr and not path.has_entity: 113 if isinstance(path, TokenRegistry): 114 raise sa_exc.ArgumentError( 115 "Wildcard token cannot be followed by another entity") 116 else: 117 raise sa_exc.ArgumentError( 118 "Attribute '%s' of entity '%s' does not " 119 "refer to a mapped entity" % 120 (path.prop.key, path.parent.entity) 121 ) 122 123 if isinstance(attr, util.string_types): 124 default_token = attr.endswith(_DEFAULT_TOKEN) 125 if attr.endswith(_WILDCARD_TOKEN) or default_token: 126 if default_token: 127 self.propagate_to_loaders = False 128 if wildcard_key: 129 attr = "%s:%s" % (wildcard_key, attr) 130 return path.token(attr) 131 132 try: 133 # use getattr on the class to work around 134 # synonyms, hybrids, etc. 135 attr = getattr(path.entity.class_, attr) 136 except AttributeError: 137 if raiseerr: 138 raise sa_exc.ArgumentError( 139 "Can't find property named '%s' on the " 140 "mapped entity %s in this Query. " % ( 141 attr, path.entity) 142 ) 143 else: 144 return None 145 else: 146 attr = attr.property 147 148 path = path[attr] 149 else: 150 prop = attr.property 151 152 if not prop.parent.common_parent(path.mapper): 153 if raiseerr: 154 raise sa_exc.ArgumentError( 155 "Attribute '%s' does not " 156 "link from element '%s'" % (attr, path.entity)) 157 else: 158 return None 159 160 if getattr(attr, '_of_type', None): 161 ac = attr._of_type 162 ext_info = inspect(ac) 163 164 path_element = ext_info.mapper 165 existing = path.entity_path[prop].get( 166 self.context, "path_with_polymorphic") 167 if not ext_info.is_aliased_class: 168 ac = orm_util.with_polymorphic( 169 ext_info.mapper.base_mapper, 170 ext_info.mapper, aliased=True, 171 _use_mapper_path=True, 172 _existing_alias=existing) 173 path.entity_path[prop].set( 174 self.context, "path_with_polymorphic", inspect(ac)) 175 path = path[prop][path_element] 176 else: 177 path = path[prop] 178 179 if path.has_entity: 180 path = path.entity_path 181 return path 182 183 def __str__(self): 184 return "Load(strategy=%r)" % (self.strategy, ) 185 186 def _coerce_strat(self, strategy): 187 if strategy is not None: 188 strategy = tuple(sorted(strategy.items())) 189 return strategy 190 191 @_generative 192 def set_relationship_strategy( 193 self, attr, strategy, propagate_to_loaders=True): 194 strategy = self._coerce_strat(strategy) 195 196 self.propagate_to_loaders = propagate_to_loaders 197 # if the path is a wildcard, this will set propagate_to_loaders=False 198 self.path = self._generate_path(self.path, attr, "relationship") 199 self.strategy = strategy 200 if strategy is not None: 201 self._set_path_strategy() 202 203 @_generative 204 def set_column_strategy(self, attrs, strategy, opts=None): 205 strategy = self._coerce_strat(strategy) 206 207 for attr in attrs: 208 path = self._generate_path(self.path, attr, "column") 209 cloned = self._generate() 210 cloned.strategy = strategy 211 cloned.path = path 212 cloned.propagate_to_loaders = True 213 if opts: 214 cloned.local_opts.update(opts) 215 cloned._set_path_strategy() 216 217 def _set_path_strategy(self): 218 if self._merge_into_path: 219 # special helper for undefer_group 220 existing = self.path.get(self.context, "loader") 221 if existing: 222 existing.local_opts.update(self.local_opts) 223 else: 224 self.path.set(self.context, "loader", self) 225 226 elif self.path.has_entity: 227 self.path.parent.set(self.context, "loader", self) 228 else: 229 self.path.set(self.context, "loader", self) 230 231 def __getstate__(self): 232 d = self.__dict__.copy() 233 d["path"] = self.path.serialize() 234 return d 235 236 def __setstate__(self, state): 237 self.__dict__.update(state) 238 self.path = PathRegistry.deserialize(self.path) 239 240 def _chop_path(self, to_chop, path): 241 i = -1 242 243 for i, (c_token, p_token) in enumerate(zip(to_chop, path.path)): 244 if isinstance(c_token, util.string_types): 245 # TODO: this is approximated from the _UnboundLoad 246 # version and probably has issues, not fully covered. 247 248 if i == 0 and c_token.endswith(':' + _DEFAULT_TOKEN): 249 return to_chop 250 elif c_token != 'relationship:%s' % (_WILDCARD_TOKEN,) and \ 251 c_token != p_token.key: 252 return None 253 254 if c_token is p_token: 255 continue 256 else: 257 return None 258 return to_chop[i + 1:] 259 260 261class _UnboundLoad(Load): 262 """Represent a loader option that isn't tied to a root entity. 263 264 The loader option will produce an entity-linked :class:`.Load` 265 object when it is passed :meth:`.Query.options`. 266 267 This provides compatibility with the traditional system 268 of freestanding options, e.g. ``joinedload('x.y.z')``. 269 270 """ 271 272 def __init__(self): 273 self.path = () 274 self._to_bind = set() 275 self.local_opts = {} 276 277 _is_chain_link = False 278 279 def _set_path_strategy(self): 280 self._to_bind.add(self) 281 282 def _generate_path(self, path, attr, wildcard_key): 283 if wildcard_key and isinstance(attr, util.string_types) and \ 284 attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN): 285 if attr == _DEFAULT_TOKEN: 286 self.propagate_to_loaders = False 287 attr = "%s:%s" % (wildcard_key, attr) 288 289 return path + (attr, ) 290 291 def __getstate__(self): 292 d = self.__dict__.copy() 293 d['path'] = ret = [] 294 for token in util.to_list(self.path): 295 if isinstance(token, PropComparator): 296 ret.append((token._parentmapper.class_, token.key)) 297 else: 298 ret.append(token) 299 return d 300 301 def __setstate__(self, state): 302 ret = [] 303 for key in state['path']: 304 if isinstance(key, tuple): 305 cls, propkey = key 306 ret.append(getattr(cls, propkey)) 307 else: 308 ret.append(key) 309 state['path'] = tuple(ret) 310 self.__dict__ = state 311 312 def _process(self, query, raiseerr): 313 for val in self._to_bind: 314 val._bind_loader(query, query._attributes, raiseerr) 315 316 @classmethod 317 def _from_keys(self, meth, keys, chained, kw): 318 opt = _UnboundLoad() 319 320 def _split_key(key): 321 if isinstance(key, util.string_types): 322 # coerce fooload('*') into "default loader strategy" 323 if key == _WILDCARD_TOKEN: 324 return (_DEFAULT_TOKEN, ) 325 # coerce fooload(".*") into "wildcard on default entity" 326 elif key.startswith("." + _WILDCARD_TOKEN): 327 key = key[1:] 328 return key.split(".") 329 else: 330 return (key,) 331 all_tokens = [token for key in keys for token in _split_key(key)] 332 333 for token in all_tokens[0:-1]: 334 if chained: 335 opt = meth(opt, token, **kw) 336 else: 337 opt = opt.defaultload(token) 338 opt._is_chain_link = True 339 340 opt = meth(opt, all_tokens[-1], **kw) 341 opt._is_chain_link = False 342 343 return opt 344 345 def _chop_path(self, to_chop, path): 346 i = -1 347 for i, (c_token, (p_mapper, p_prop)) in enumerate( 348 zip(to_chop, path.pairs())): 349 if isinstance(c_token, util.string_types): 350 if i == 0 and c_token.endswith(':' + _DEFAULT_TOKEN): 351 return to_chop 352 elif c_token != 'relationship:%s' % ( 353 _WILDCARD_TOKEN,) and c_token != p_prop.key: 354 return None 355 elif isinstance(c_token, PropComparator): 356 if c_token.property is not p_prop: 357 return None 358 else: 359 i += 1 360 361 return to_chop[i:] 362 363 def _bind_loader(self, query, context, raiseerr): 364 start_path = self.path 365 # _current_path implies we're in a 366 # secondary load with an existing path 367 368 current_path = query._current_path 369 if current_path: 370 start_path = self._chop_path(start_path, current_path) 371 372 if not start_path: 373 return None 374 375 token = start_path[0] 376 377 if isinstance(token, util.string_types): 378 entity = self._find_entity_basestring(query, token, raiseerr) 379 elif isinstance(token, PropComparator): 380 prop = token.property 381 entity = self._find_entity_prop_comparator( 382 query, 383 prop.key, 384 token._parententity, 385 raiseerr) 386 387 else: 388 raise sa_exc.ArgumentError( 389 "mapper option expects " 390 "string key or list of attributes") 391 392 if not entity: 393 return 394 395 path_element = entity.entity_zero 396 397 # transfer our entity-less state into a Load() object 398 # with a real entity path. 399 loader = Load(path_element) 400 loader.context = context 401 loader.strategy = self.strategy 402 403 path = loader.path 404 for token in start_path: 405 loader.path = path = loader._generate_path( 406 loader.path, token, None, raiseerr) 407 if path is None: 408 return 409 410 loader.local_opts.update(self.local_opts) 411 412 if loader.path.has_entity: 413 effective_path = loader.path.parent 414 else: 415 effective_path = loader.path 416 417 # prioritize "first class" options over those 418 # that were "links in the chain", e.g. "x" and "y" in 419 # someload("x.y.z") versus someload("x") / someload("x.y") 420 421 if effective_path.is_token: 422 for path in effective_path.generate_for_superclasses(): 423 if self._merge_into_path: 424 # special helper for undefer_group 425 existing = path.get(context, "loader") 426 if existing: 427 existing.local_opts.update(self.local_opts) 428 else: 429 path.set(context, "loader", loader) 430 elif self._is_chain_link: 431 path.setdefault(context, "loader", loader) 432 else: 433 path.set(context, "loader", loader) 434 else: 435 # only supported for the undefer_group() wildcard opt 436 assert not self._merge_into_path 437 if self._is_chain_link: 438 effective_path.setdefault(context, "loader", loader) 439 else: 440 effective_path.set(context, "loader", loader) 441 442 def _find_entity_prop_comparator(self, query, token, mapper, raiseerr): 443 if _is_aliased_class(mapper): 444 searchfor = mapper 445 else: 446 searchfor = _class_to_mapper(mapper) 447 for ent in query._mapper_entities: 448 if ent.corresponds_to(searchfor): 449 return ent 450 else: 451 if raiseerr: 452 if not list(query._mapper_entities): 453 raise sa_exc.ArgumentError( 454 "Query has only expression-based entities - " 455 "can't find property named '%s'." 456 % (token, ) 457 ) 458 else: 459 raise sa_exc.ArgumentError( 460 "Can't find property '%s' on any entity " 461 "specified in this Query. Note the full path " 462 "from root (%s) to target entity must be specified." 463 % (token, ",".join(str(x) for 464 x in query._mapper_entities)) 465 ) 466 else: 467 return None 468 469 def _find_entity_basestring(self, query, token, raiseerr): 470 if token.endswith(':' + _WILDCARD_TOKEN): 471 if len(list(query._mapper_entities)) != 1: 472 if raiseerr: 473 raise sa_exc.ArgumentError( 474 "Wildcard loader can only be used with exactly " 475 "one entity. Use Load(ent) to specify " 476 "specific entities.") 477 elif token.endswith(_DEFAULT_TOKEN): 478 raiseerr = False 479 480 for ent in query._mapper_entities: 481 # return only the first _MapperEntity when searching 482 # based on string prop name. Ideally object 483 # attributes are used to specify more exactly. 484 return ent 485 else: 486 if raiseerr: 487 raise sa_exc.ArgumentError( 488 "Query has only expression-based entities - " 489 "can't find property named '%s'." 490 % (token, ) 491 ) 492 else: 493 return None 494 495 496class loader_option(object): 497 def __init__(self): 498 pass 499 500 def __call__(self, fn): 501 self.name = name = fn.__name__ 502 self.fn = fn 503 if hasattr(Load, name): 504 raise TypeError("Load class already has a %s method." % (name)) 505 setattr(Load, name, fn) 506 507 return self 508 509 def _add_unbound_fn(self, fn): 510 self._unbound_fn = fn 511 fn_doc = self.fn.__doc__ 512 self.fn.__doc__ = """Produce a new :class:`.Load` object with the 513:func:`.orm.%(name)s` option applied. 514 515See :func:`.orm.%(name)s` for usage examples. 516 517""" % {"name": self.name} 518 519 fn.__doc__ = fn_doc 520 return self 521 522 def _add_unbound_all_fn(self, fn): 523 self._unbound_all_fn = fn 524 fn.__doc__ = """Produce a standalone "all" option for :func:`.orm.%(name)s`. 525 526.. deprecated:: 0.9.0 527 528 The "_all()" style is replaced by method chaining, e.g.:: 529 530 session.query(MyClass).options( 531 %(name)s("someattribute").%(name)s("anotherattribute") 532 ) 533 534""" % {"name": self.name} 535 return self 536 537 538@loader_option() 539def contains_eager(loadopt, attr, alias=None): 540 """Indicate that the given attribute should be eagerly loaded from 541 columns stated manually in the query. 542 543 This function is part of the :class:`.Load` interface and supports 544 both method-chained and standalone operation. 545 546 The option is used in conjunction with an explicit join that loads 547 the desired rows, i.e.:: 548 549 sess.query(Order).\\ 550 join(Order.user).\\ 551 options(contains_eager(Order.user)) 552 553 The above query would join from the ``Order`` entity to its related 554 ``User`` entity, and the returned ``Order`` objects would have the 555 ``Order.user`` attribute pre-populated. 556 557 :func:`contains_eager` also accepts an `alias` argument, which is the 558 string name of an alias, an :func:`~sqlalchemy.sql.expression.alias` 559 construct, or an :func:`~sqlalchemy.orm.aliased` construct. Use this when 560 the eagerly-loaded rows are to come from an aliased table:: 561 562 user_alias = aliased(User) 563 sess.query(Order).\\ 564 join((user_alias, Order.user)).\\ 565 options(contains_eager(Order.user, alias=user_alias)) 566 567 .. seealso:: 568 569 :ref:`contains_eager` 570 571 """ 572 if alias is not None: 573 if not isinstance(alias, str): 574 info = inspect(alias) 575 alias = info.selectable 576 577 cloned = loadopt.set_relationship_strategy( 578 attr, 579 {"lazy": "joined"}, 580 propagate_to_loaders=False 581 ) 582 cloned.local_opts['eager_from_alias'] = alias 583 return cloned 584 585 586@contains_eager._add_unbound_fn 587def contains_eager(*keys, **kw): 588 return _UnboundLoad()._from_keys( 589 _UnboundLoad.contains_eager, keys, True, kw) 590 591 592@loader_option() 593def load_only(loadopt, *attrs): 594 """Indicate that for a particular entity, only the given list 595 of column-based attribute names should be loaded; all others will be 596 deferred. 597 598 This function is part of the :class:`.Load` interface and supports 599 both method-chained and standalone operation. 600 601 Example - given a class ``User``, load only the ``name`` and ``fullname`` 602 attributes:: 603 604 session.query(User).options(load_only("name", "fullname")) 605 606 Example - given a relationship ``User.addresses -> Address``, specify 607 subquery loading for the ``User.addresses`` collection, but on each 608 ``Address`` object load only the ``email_address`` attribute:: 609 610 session.query(User).options( 611 subqueryload("addresses").load_only("email_address") 612 ) 613 614 For a :class:`.Query` that has multiple entities, the lead entity can be 615 specifically referred to using the :class:`.Load` constructor:: 616 617 session.query(User, Address).join(User.addresses).options( 618 Load(User).load_only("name", "fullname"), 619 Load(Address).load_only("email_addres") 620 ) 621 622 623 .. versionadded:: 0.9.0 624 625 """ 626 cloned = loadopt.set_column_strategy( 627 attrs, 628 {"deferred": False, "instrument": True} 629 ) 630 cloned.set_column_strategy("*", 631 {"deferred": True, "instrument": True}, 632 {"undefer_pks": True}) 633 return cloned 634 635 636@load_only._add_unbound_fn 637def load_only(*attrs): 638 return _UnboundLoad().load_only(*attrs) 639 640 641@loader_option() 642def joinedload(loadopt, attr, innerjoin=None): 643 """Indicate that the given attribute should be loaded using joined 644 eager loading. 645 646 This function is part of the :class:`.Load` interface and supports 647 both method-chained and standalone operation. 648 649 examples:: 650 651 # joined-load the "orders" collection on "User" 652 query(User).options(joinedload(User.orders)) 653 654 # joined-load Order.items and then Item.keywords 655 query(Order).options(joinedload(Order.items).joinedload(Item.keywords)) 656 657 # lazily load Order.items, but when Items are loaded, 658 # joined-load the keywords collection 659 query(Order).options(lazyload(Order.items).joinedload(Item.keywords)) 660 661 :param innerjoin: if ``True``, indicates that the joined eager load should 662 use an inner join instead of the default of left outer join:: 663 664 query(Order).options(joinedload(Order.user, innerjoin=True)) 665 666 In order to chain multiple eager joins together where some may be 667 OUTER and others INNER, right-nested joins are used to link them:: 668 669 query(A).options( 670 joinedload(A.bs, innerjoin=False). 671 joinedload(B.cs, innerjoin=True) 672 ) 673 674 The above query, linking A.bs via "outer" join and B.cs via "inner" join 675 would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When using 676 SQLite, this form of JOIN is translated to use full subqueries as this 677 syntax is otherwise not directly supported. 678 679 The ``innerjoin`` flag can also be stated with the term ``"unnested"``. 680 This will prevent joins from being right-nested, and will instead 681 link an "innerjoin" eagerload to an "outerjoin" eagerload by bypassing 682 the "inner" join. Using this form as follows:: 683 684 query(A).options( 685 joinedload(A.bs, innerjoin=False). 686 joinedload(B.cs, innerjoin="unnested") 687 ) 688 689 Joins will be rendered as "a LEFT OUTER JOIN b LEFT OUTER JOIN c", so that 690 all of "a" is matched rather than being incorrectly limited by a "b" that 691 does not contain a "c". 692 693 .. note:: The "unnested" flag does **not** affect the JOIN rendered 694 from a many-to-many association table, e.g. a table configured 695 as :paramref:`.relationship.secondary`, to the target table; for 696 correctness of results, these joins are always INNER and are 697 therefore right-nested if linked to an OUTER join. 698 699 .. versionadded:: 0.9.4 Added support for "nesting" of eager "inner" 700 joins. See :ref:`feature_2976`. 701 702 .. versionchanged:: 1.0.0 ``innerjoin=True`` now implies 703 ``innerjoin="nested"``, whereas in 0.9 it implied 704 ``innerjoin="unnested"``. In order to achieve the pre-1.0 "unnested" 705 inner join behavior, use the value ``innerjoin="unnested"``. 706 See :ref:`migration_3008`. 707 708 .. note:: 709 710 The joins produced by :func:`.orm.joinedload` are **anonymously 711 aliased**. The criteria by which the join proceeds cannot be 712 modified, nor can the :class:`.Query` refer to these joins in any way, 713 including ordering. 714 715 To produce a specific SQL JOIN which is explicitly available, use 716 :meth:`.Query.join`. To combine explicit JOINs with eager loading 717 of collections, use :func:`.orm.contains_eager`; see 718 :ref:`contains_eager`. 719 720 .. seealso:: 721 722 :ref:`loading_toplevel` 723 724 :ref:`contains_eager` 725 726 :func:`.orm.subqueryload` 727 728 :func:`.orm.lazyload` 729 730 :paramref:`.relationship.lazy` 731 732 :paramref:`.relationship.innerjoin` - :func:`.relationship`-level 733 version of the :paramref:`.joinedload.innerjoin` option. 734 735 """ 736 loader = loadopt.set_relationship_strategy(attr, {"lazy": "joined"}) 737 if innerjoin is not None: 738 loader.local_opts['innerjoin'] = innerjoin 739 return loader 740 741 742@joinedload._add_unbound_fn 743def joinedload(*keys, **kw): 744 return _UnboundLoad._from_keys( 745 _UnboundLoad.joinedload, keys, False, kw) 746 747 748@joinedload._add_unbound_all_fn 749def joinedload_all(*keys, **kw): 750 return _UnboundLoad._from_keys( 751 _UnboundLoad.joinedload, keys, True, kw) 752 753 754@loader_option() 755def subqueryload(loadopt, attr): 756 """Indicate that the given attribute should be loaded using 757 subquery eager loading. 758 759 This function is part of the :class:`.Load` interface and supports 760 both method-chained and standalone operation. 761 762 examples:: 763 764 # subquery-load the "orders" collection on "User" 765 query(User).options(subqueryload(User.orders)) 766 767 # subquery-load Order.items and then Item.keywords 768 query(Order).options(subqueryload(Order.items).subqueryload(Item.keywords)) 769 770 # lazily load Order.items, but when Items are loaded, 771 # subquery-load the keywords collection 772 query(Order).options(lazyload(Order.items).subqueryload(Item.keywords)) 773 774 775 .. seealso:: 776 777 :ref:`loading_toplevel` 778 779 :func:`.orm.joinedload` 780 781 :func:`.orm.lazyload` 782 783 :paramref:`.relationship.lazy` 784 785 """ 786 return loadopt.set_relationship_strategy(attr, {"lazy": "subquery"}) 787 788 789@subqueryload._add_unbound_fn 790def subqueryload(*keys): 791 return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, False, {}) 792 793 794@subqueryload._add_unbound_all_fn 795def subqueryload_all(*keys): 796 return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, True, {}) 797 798 799@loader_option() 800def lazyload(loadopt, attr): 801 """Indicate that the given attribute should be loaded using "lazy" 802 loading. 803 804 This function is part of the :class:`.Load` interface and supports 805 both method-chained and standalone operation. 806 807 .. seealso:: 808 809 :paramref:`.relationship.lazy` 810 811 """ 812 return loadopt.set_relationship_strategy(attr, {"lazy": "select"}) 813 814 815@lazyload._add_unbound_fn 816def lazyload(*keys): 817 return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, False, {}) 818 819 820@lazyload._add_unbound_all_fn 821def lazyload_all(*keys): 822 return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, True, {}) 823 824 825@loader_option() 826def immediateload(loadopt, attr): 827 """Indicate that the given attribute should be loaded using 828 an immediate load with a per-attribute SELECT statement. 829 830 This function is part of the :class:`.Load` interface and supports 831 both method-chained and standalone operation. 832 833 .. seealso:: 834 835 :ref:`loading_toplevel` 836 837 :func:`.orm.joinedload` 838 839 :func:`.orm.lazyload` 840 841 :paramref:`.relationship.lazy` 842 843 """ 844 loader = loadopt.set_relationship_strategy(attr, {"lazy": "immediate"}) 845 return loader 846 847 848@immediateload._add_unbound_fn 849def immediateload(*keys): 850 return _UnboundLoad._from_keys( 851 _UnboundLoad.immediateload, keys, False, {}) 852 853 854@loader_option() 855def noload(loadopt, attr): 856 """Indicate that the given relationship attribute should remain unloaded. 857 858 This function is part of the :class:`.Load` interface and supports 859 both method-chained and standalone operation. 860 861 :func:`.orm.noload` applies to :func:`.relationship` attributes; for 862 column-based attributes, see :func:`.orm.defer`. 863 864 """ 865 866 return loadopt.set_relationship_strategy(attr, {"lazy": "noload"}) 867 868 869@noload._add_unbound_fn 870def noload(*keys): 871 return _UnboundLoad._from_keys(_UnboundLoad.noload, keys, False, {}) 872 873 874@loader_option() 875def defaultload(loadopt, attr): 876 """Indicate an attribute should load using its default loader style. 877 878 This method is used to link to other loader options, such as 879 to set the :func:`.orm.defer` option on a class that is linked to 880 a relationship of the parent class being loaded, :func:`.orm.defaultload` 881 can be used to navigate this path without changing the loading style 882 of the relationship:: 883 884 session.query(MyClass).options(defaultload("someattr").defer("some_column")) 885 886 .. seealso:: 887 888 :func:`.orm.defer` 889 890 :func:`.orm.undefer` 891 892 """ 893 return loadopt.set_relationship_strategy( 894 attr, 895 None 896 ) 897 898 899@defaultload._add_unbound_fn 900def defaultload(*keys): 901 return _UnboundLoad._from_keys(_UnboundLoad.defaultload, keys, False, {}) 902 903 904@loader_option() 905def defer(loadopt, key): 906 """Indicate that the given column-oriented attribute should be deferred, e.g. 907 not loaded until accessed. 908 909 This function is part of the :class:`.Load` interface and supports 910 both method-chained and standalone operation. 911 912 e.g.:: 913 914 from sqlalchemy.orm import defer 915 916 session.query(MyClass).options( 917 defer("attribute_one"), 918 defer("attribute_two")) 919 920 session.query(MyClass).options( 921 defer(MyClass.attribute_one), 922 defer(MyClass.attribute_two)) 923 924 To specify a deferred load of an attribute on a related class, 925 the path can be specified one token at a time, specifying the loading 926 style for each link along the chain. To leave the loading style 927 for a link unchanged, use :func:`.orm.defaultload`:: 928 929 session.query(MyClass).options(defaultload("someattr").defer("some_column")) 930 931 A :class:`.Load` object that is present on a certain path can have 932 :meth:`.Load.defer` called multiple times, each will operate on the same 933 parent entity:: 934 935 936 session.query(MyClass).options( 937 defaultload("someattr"). 938 defer("some_column"). 939 defer("some_other_column"). 940 defer("another_column") 941 ) 942 943 :param key: Attribute to be deferred. 944 945 :param \*addl_attrs: Deprecated; this option supports the old 0.8 style 946 of specifying a path as a series of attributes, which is now superseded 947 by the method-chained style. 948 949 .. seealso:: 950 951 :ref:`deferred` 952 953 :func:`.orm.undefer` 954 955 """ 956 return loadopt.set_column_strategy( 957 (key, ), 958 {"deferred": True, "instrument": True} 959 ) 960 961 962@defer._add_unbound_fn 963def defer(key, *addl_attrs): 964 return _UnboundLoad._from_keys( 965 _UnboundLoad.defer, (key, ) + addl_attrs, False, {}) 966 967 968@loader_option() 969def undefer(loadopt, key): 970 """Indicate that the given column-oriented attribute should be undeferred, 971 e.g. specified within the SELECT statement of the entity as a whole. 972 973 The column being undeferred is typically set up on the mapping as a 974 :func:`.deferred` attribute. 975 976 This function is part of the :class:`.Load` interface and supports 977 both method-chained and standalone operation. 978 979 Examples:: 980 981 # undefer two columns 982 session.query(MyClass).options(undefer("col1"), undefer("col2")) 983 984 # undefer all columns specific to a single class using Load + * 985 session.query(MyClass, MyOtherClass).options( 986 Load(MyClass).undefer("*")) 987 988 :param key: Attribute to be undeferred. 989 990 :param \*addl_attrs: Deprecated; this option supports the old 0.8 style 991 of specifying a path as a series of attributes, which is now superseded 992 by the method-chained style. 993 994 .. seealso:: 995 996 :ref:`deferred` 997 998 :func:`.orm.defer` 999 1000 :func:`.orm.undefer_group` 1001 1002 """ 1003 return loadopt.set_column_strategy( 1004 (key, ), 1005 {"deferred": False, "instrument": True} 1006 ) 1007 1008 1009@undefer._add_unbound_fn 1010def undefer(key, *addl_attrs): 1011 return _UnboundLoad._from_keys( 1012 _UnboundLoad.undefer, (key, ) + addl_attrs, False, {}) 1013 1014 1015@loader_option() 1016def undefer_group(loadopt, name): 1017 """Indicate that columns within the given deferred group name should be 1018 undeferred. 1019 1020 The columns being undeferred are set up on the mapping as 1021 :func:`.deferred` attributes and include a "group" name. 1022 1023 E.g:: 1024 1025 session.query(MyClass).options(undefer_group("large_attrs")) 1026 1027 To undefer a group of attributes on a related entity, the path can be 1028 spelled out using relationship loader options, such as 1029 :func:`.orm.defaultload`:: 1030 1031 session.query(MyClass).options( 1032 defaultload("someattr").undefer_group("large_attrs")) 1033 1034 .. versionchanged:: 0.9.0 :func:`.orm.undefer_group` is now specific to a 1035 particiular entity load path. 1036 1037 .. seealso:: 1038 1039 :ref:`deferred` 1040 1041 :func:`.orm.defer` 1042 1043 :func:`.orm.undefer` 1044 1045 """ 1046 loadopt._merge_into_path = True 1047 return loadopt.set_column_strategy( 1048 "*", 1049 None, 1050 {"undefer_group_%s" % name: True} 1051 ) 1052 1053 1054@undefer_group._add_unbound_fn 1055def undefer_group(name): 1056 return _UnboundLoad().undefer_group(name) 1057