Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/strategy_options.py: 25%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

650 statements  

1# orm/strategy_options.py 

2# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7 

8""" 

9 

10""" 

11 

12from . import util as orm_util 

13from .attributes import QueryableAttribute 

14from .base import _class_to_mapper 

15from .base import _is_aliased_class 

16from .base import _is_mapped_class 

17from .base import InspectionAttr 

18from .interfaces import LoaderOption 

19from .interfaces import MapperProperty 

20from .interfaces import PropComparator 

21from .path_registry import _DEFAULT_TOKEN 

22from .path_registry import _WILDCARD_TOKEN 

23from .path_registry import PathRegistry 

24from .path_registry import TokenRegistry 

25from .util import _orm_full_deannotate 

26from .. import exc as sa_exc 

27from .. import inspect 

28from .. import util 

29from ..sql import and_ 

30from ..sql import coercions 

31from ..sql import roles 

32from ..sql import traversals 

33from ..sql import visitors 

34from ..sql.base import _generative 

35from ..sql.base import Generative 

36 

37 

38class Load(Generative, LoaderOption): 

39 """Represents loader options which modify the state of a 

40 :class:`_query.Query` in order to affect how various mapped attributes are 

41 loaded. 

42 

43 The :class:`_orm.Load` object is in most cases used implicitly behind the 

44 scenes when one makes use of a query option like :func:`_orm.joinedload`, 

45 :func:`.defer`, or similar. However, the :class:`_orm.Load` object 

46 can also be used directly, and in some cases can be useful. 

47 

48 To use :class:`_orm.Load` directly, instantiate it with the target mapped 

49 class as the argument. This style of usage is 

50 useful when dealing with a :class:`_query.Query` 

51 that has multiple entities:: 

52 

53 myopt = Load(MyClass).joinedload("widgets") 

54 

55 The above ``myopt`` can now be used with :meth:`_query.Query.options`, 

56 where it 

57 will only take effect for the ``MyClass`` entity:: 

58 

59 session.query(MyClass, MyOtherClass).options(myopt) 

60 

61 One case where :class:`_orm.Load` 

62 is useful as public API is when specifying 

63 "wildcard" options that only take effect for a certain class:: 

64 

65 session.query(Order).options(Load(Order).lazyload('*')) 

66 

67 Above, all relationships on ``Order`` will be lazy-loaded, but other 

68 attributes on those descendant objects will load using their normal 

69 loader strategy. 

70 

71 .. seealso:: 

72 

73 :ref:`deferred_options` 

74 

75 :ref:`deferred_loading_w_multiple` 

76 

77 :ref:`relationship_loader_options` 

78 

79 """ 

80 

81 _is_strategy_option = True 

82 

83 _cache_key_traversal = [ 

84 ("path", visitors.ExtendedInternalTraversal.dp_has_cache_key), 

85 ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj), 

86 ("_of_type", visitors.ExtendedInternalTraversal.dp_multi), 

87 ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list), 

88 ( 

89 "_context_cache_key", 

90 visitors.ExtendedInternalTraversal.dp_has_cache_key_tuples, 

91 ), 

92 ( 

93 "local_opts", 

94 visitors.ExtendedInternalTraversal.dp_string_multi_dict, 

95 ), 

96 ] 

97 

98 def __init__(self, entity): 

99 insp = inspect(entity) 

100 insp._post_inspect 

101 

102 self.path = insp._path_registry 

103 # note that this .context is shared among all descendant 

104 # Load objects 

105 self.context = util.OrderedDict() 

106 self.local_opts = {} 

107 self.is_class_strategy = False 

108 

109 @classmethod 

110 def for_existing_path(cls, path): 

111 load = cls.__new__(cls) 

112 load.path = path 

113 load.context = {} 

114 load.local_opts = {} 

115 load._of_type = None 

116 load._extra_criteria = () 

117 return load 

118 

119 def _adapt_cached_option_to_uncached_option(self, context, uncached_opt): 

120 return self._adjust_for_extra_criteria(context) 

121 

122 def _generate_extra_criteria(self, context): 

123 """Apply the current bound parameters in a QueryContext to the 

124 immediate "extra_criteria" stored with this Load object. 

125 

126 Load objects are typically pulled from the cached version of 

127 the statement from a QueryContext. The statement currently being 

128 executed will have new values (and keys) for bound parameters in the 

129 extra criteria which need to be applied by loader strategies when 

130 they handle this criteria for a result set. 

131 

132 """ 

133 

134 assert ( 

135 self._extra_criteria 

136 ), "this should only be called if _extra_criteria is present" 

137 

138 orig_query = context.compile_state.select_statement 

139 current_query = context.query 

140 

141 # NOTE: while it seems like we should not do the "apply" operation 

142 # here if orig_query is current_query, skipping it in the "optimized" 

143 # case causes the query to be different from a cache key perspective, 

144 # because we are creating a copy of the criteria which is no longer 

145 # the same identity of the _extra_criteria in the loader option 

146 # itself. cache key logic produces a different key for 

147 # (A, copy_of_A) vs. (A, A), because in the latter case it shortens 

148 # the second part of the key to just indicate on identity. 

149 

150 # if orig_query is current_query: 

151 # not cached yet. just do the and_() 

152 # return and_(*self._extra_criteria) 

153 

154 k1 = orig_query._generate_cache_key() 

155 k2 = current_query._generate_cache_key() 

156 

157 return k2._apply_params_to_element(k1, and_(*self._extra_criteria)) 

158 

159 def _adjust_for_extra_criteria(self, context): 

160 """Apply the current bound parameters in a QueryContext to all 

161 occurrences "extra_criteria" stored within al this Load object; 

162 copying in place. 

163 

164 """ 

165 orig_query = context.compile_state.select_statement 

166 

167 applied = {} 

168 

169 ck = [None, None] 

170 

171 def process(opt): 

172 if not opt._extra_criteria: 

173 return 

174 

175 if ck[0] is None: 

176 ck[:] = ( 

177 orig_query._generate_cache_key(), 

178 context.query._generate_cache_key(), 

179 ) 

180 k1, k2 = ck 

181 

182 opt._extra_criteria = tuple( 

183 k2._apply_params_to_element(k1, crit) 

184 for crit in opt._extra_criteria 

185 ) 

186 

187 return self._deep_clone(applied, process) 

188 

189 def _deep_clone(self, applied, process): 

190 if self in applied: 

191 return applied[self] 

192 

193 cloned = self._generate() 

194 

195 applied[self] = cloned 

196 

197 cloned.strategy = self.strategy 

198 

199 assert cloned.propagate_to_loaders == self.propagate_to_loaders 

200 assert cloned.is_class_strategy == self.is_class_strategy 

201 assert cloned.is_opts_only == self.is_opts_only 

202 

203 if self.context: 

204 cloned.context = util.OrderedDict( 

205 [ 

206 ( 

207 key, 

208 value._deep_clone(applied, process) 

209 if isinstance(value, Load) 

210 else value, 

211 ) 

212 for key, value in self.context.items() 

213 ] 

214 ) 

215 

216 cloned.local_opts.update(self.local_opts) 

217 

218 process(cloned) 

219 

220 return cloned 

221 

222 @property 

223 def _context_cache_key(self): 

224 serialized = [] 

225 if self.context is None: 

226 return [] 

227 for (key, loader_path), obj in self.context.items(): 

228 if key != "loader": 

229 continue 

230 serialized.append(loader_path + (obj,)) 

231 return serialized 

232 

233 def _generate(self): 

234 cloned = super(Load, self)._generate() 

235 cloned.local_opts = {} 

236 return cloned 

237 

238 is_opts_only = False 

239 is_class_strategy = False 

240 strategy = None 

241 propagate_to_loaders = False 

242 _of_type = None 

243 _extra_criteria = () 

244 

245 def process_compile_state_replaced_entities( 

246 self, compile_state, mapper_entities 

247 ): 

248 if not compile_state.compile_options._enable_eagerloads: 

249 return 

250 

251 # process is being run here so that the options given are validated 

252 # against what the lead entities were, as well as to accommodate 

253 # for the entities having been replaced with equivalents 

254 self._process( 

255 compile_state, 

256 mapper_entities, 

257 not bool(compile_state.current_path), 

258 ) 

259 

260 def process_compile_state(self, compile_state): 

261 if not compile_state.compile_options._enable_eagerloads: 

262 return 

263 

264 self._process( 

265 compile_state, 

266 compile_state._lead_mapper_entities, 

267 not bool(compile_state.current_path) 

268 and not compile_state.compile_options._for_refresh_state, 

269 ) 

270 

271 def _process(self, compile_state, mapper_entities, raiseerr): 

272 is_refresh = compile_state.compile_options._for_refresh_state 

273 current_path = compile_state.current_path 

274 if current_path: 

275 for (token, start_path), loader in self.context.items(): 

276 if is_refresh and not loader.propagate_to_loaders: 

277 continue 

278 chopped_start_path = self._chop_path(start_path, current_path) 

279 if chopped_start_path is not None: 

280 compile_state.attributes[ 

281 (token, chopped_start_path) 

282 ] = loader 

283 else: 

284 compile_state.attributes.update(self.context) 

285 

286 def _generate_path( 

287 self, 

288 path, 

289 attr, 

290 for_strategy, 

291 wildcard_key, 

292 raiseerr=True, 

293 polymorphic_entity_context=None, 

294 ): 

295 existing_of_type = self._of_type 

296 self._of_type = None 

297 if raiseerr and not path.has_entity: 

298 if isinstance(path, TokenRegistry): 

299 raise sa_exc.ArgumentError( 

300 "Wildcard token cannot be followed by another entity" 

301 ) 

302 else: 

303 raise sa_exc.ArgumentError( 

304 "Mapped attribute '%s' does not " 

305 "refer to a mapped entity" % (path.prop,) 

306 ) 

307 

308 if isinstance(attr, util.string_types): 

309 

310 default_token = attr.endswith(_DEFAULT_TOKEN) 

311 attr_str_name = attr 

312 if attr.endswith(_WILDCARD_TOKEN) or default_token: 

313 if default_token: 

314 self.propagate_to_loaders = False 

315 if wildcard_key: 

316 attr = "%s:%s" % (wildcard_key, attr) 

317 

318 # TODO: AliasedInsp inside the path for of_type is not 

319 # working for a with_polymorphic entity because the 

320 # relationship loaders don't render the with_poly into the 

321 # path. See #4469 which will try to improve this 

322 if existing_of_type and not existing_of_type.is_aliased_class: 

323 path = path.parent[existing_of_type] 

324 path = path.token(attr) 

325 self.path = path 

326 return path 

327 

328 if existing_of_type: 

329 ent = inspect(existing_of_type) 

330 else: 

331 ent = path.entity 

332 

333 util.warn_deprecated_20( 

334 "Using strings to indicate column or " 

335 "relationship paths in loader options is deprecated " 

336 "and will be removed in SQLAlchemy 2.0. Please use " 

337 "the class-bound attribute directly.", 

338 ) 

339 try: 

340 # use getattr on the class to work around 

341 # synonyms, hybrids, etc. 

342 attr = getattr(ent.class_, attr) 

343 except AttributeError as err: 

344 if raiseerr: 

345 util.raise_( 

346 sa_exc.ArgumentError( 

347 'Can\'t find property named "%s" on ' 

348 "%s in this Query." % (attr, ent) 

349 ), 

350 replace_context=err, 

351 ) 

352 else: 

353 return None 

354 else: 

355 try: 

356 attr = found_property = attr.property 

357 except AttributeError as ae: 

358 if not isinstance(attr, MapperProperty): 

359 util.raise_( 

360 sa_exc.ArgumentError( 

361 'Expected attribute "%s" on %s to be a ' 

362 "mapped attribute; " 

363 "instead got %s object." 

364 % (attr_str_name, ent, type(attr)) 

365 ), 

366 replace_context=ae, 

367 ) 

368 else: 

369 raise 

370 

371 path = path[attr] 

372 else: 

373 insp = inspect(attr) 

374 

375 if insp.is_mapper or insp.is_aliased_class: 

376 # TODO: this does not appear to be a valid codepath. "attr" 

377 # would never be a mapper. This block is present in 1.2 

378 # as well however does not seem to be accessed in any tests. 

379 if not orm_util._entity_corresponds_to_use_path_impl( 

380 attr.parent, path[-1] 

381 ): 

382 if raiseerr: 

383 raise sa_exc.ArgumentError( 

384 "Attribute '%s' does not " 

385 "link from element '%s'" % (attr, path.entity) 

386 ) 

387 else: 

388 return None 

389 elif insp.is_property: 

390 prop = found_property = attr 

391 path = path[prop] 

392 elif insp.is_attribute: 

393 prop = found_property = attr.property 

394 

395 if not orm_util._entity_corresponds_to_use_path_impl( 

396 attr.parent, path[-1] 

397 ): 

398 if raiseerr: 

399 raise sa_exc.ArgumentError( 

400 'Attribute "%s" does not ' 

401 'link from element "%s".%s' 

402 % ( 

403 attr, 

404 path.entity, 

405 ( 

406 " Did you mean to use " 

407 "%s.of_type(%s)?" 

408 % (path[-2], attr.class_.__name__) 

409 if len(path) > 1 

410 and path.entity.is_mapper 

411 and attr.parent.is_aliased_class 

412 else "" 

413 ), 

414 ) 

415 ) 

416 else: 

417 return None 

418 

419 if attr._extra_criteria and not self._extra_criteria: 

420 # in most cases, the process that brings us here will have 

421 # already established _extra_criteria. however if not, 

422 # and it's present on the attribute, then use that. 

423 self._extra_criteria = attr._extra_criteria 

424 

425 if getattr(attr, "_of_type", None): 

426 ac = attr._of_type 

427 ext_info = of_type_info = inspect(ac) 

428 

429 if polymorphic_entity_context is None: 

430 polymorphic_entity_context = self.context 

431 

432 existing = path.entity_path[prop].get( 

433 polymorphic_entity_context, "path_with_polymorphic" 

434 ) 

435 

436 if not ext_info.is_aliased_class: 

437 ac = orm_util.with_polymorphic( 

438 ext_info.mapper.base_mapper, 

439 ext_info.mapper, 

440 aliased=True, 

441 _use_mapper_path=True, 

442 _existing_alias=inspect(existing) 

443 if existing is not None 

444 else None, 

445 ) 

446 

447 ext_info = inspect(ac) 

448 

449 path.entity_path[prop].set( 

450 polymorphic_entity_context, "path_with_polymorphic", ac 

451 ) 

452 

453 path = path[prop][ext_info] 

454 

455 self._of_type = of_type_info 

456 

457 else: 

458 path = path[prop] 

459 

460 if for_strategy is not None: 

461 found_property._get_strategy(for_strategy) 

462 if path.has_entity: 

463 path = path.entity_path 

464 self.path = path 

465 return path 

466 

467 def __str__(self): 

468 return "Load(strategy=%r)" % (self.strategy,) 

469 

470 def _coerce_strat(self, strategy): 

471 if strategy is not None: 

472 strategy = tuple(sorted(strategy.items())) 

473 return strategy 

474 

475 def _apply_to_parent(self, parent, applied, bound): 

476 raise NotImplementedError( 

477 "Only 'unbound' loader options may be used with the " 

478 "Load.options() method" 

479 ) 

480 

481 @_generative 

482 def options(self, *opts): 

483 r"""Apply a series of options as sub-options to this 

484 :class:`_orm.Load` 

485 object. 

486 

487 E.g.:: 

488 

489 query = session.query(Author) 

490 query = query.options( 

491 joinedload(Author.book).options( 

492 load_only(Book.summary, Book.excerpt), 

493 joinedload(Book.citations).options( 

494 joinedload(Citation.author) 

495 ) 

496 ) 

497 ) 

498 

499 :param \*opts: A series of loader option objects (ultimately 

500 :class:`_orm.Load` objects) which should be applied to the path 

501 specified by this :class:`_orm.Load` object. 

502 

503 .. versionadded:: 1.3.6 

504 

505 .. seealso:: 

506 

507 :func:`.defaultload` 

508 

509 :ref:`relationship_loader_options` 

510 

511 :ref:`deferred_loading_w_multiple` 

512 

513 """ 

514 apply_cache = {} 

515 bound = not isinstance(self, _UnboundLoad) 

516 if bound: 

517 raise NotImplementedError( 

518 "The options() method is currently only supported " 

519 "for 'unbound' loader options" 

520 ) 

521 for opt in opts: 

522 try: 

523 opt._apply_to_parent(self, apply_cache, bound) 

524 except AttributeError as ae: 

525 if not isinstance(opt, Load): 

526 util.raise_( 

527 sa_exc.ArgumentError( 

528 "Loader option %s is not compatible with the " 

529 "Load.options() method." % (opt,) 

530 ), 

531 from_=ae, 

532 ) 

533 else: 

534 raise 

535 

536 @_generative 

537 def set_relationship_strategy( 

538 self, attr, strategy, propagate_to_loaders=True 

539 ): 

540 strategy = self._coerce_strat(strategy) 

541 self.propagate_to_loaders = propagate_to_loaders 

542 cloned = self._clone_for_bind_strategy(attr, strategy, "relationship") 

543 self.path = cloned.path 

544 self._of_type = cloned._of_type 

545 self._extra_criteria = cloned._extra_criteria 

546 cloned.is_class_strategy = self.is_class_strategy = False 

547 self.propagate_to_loaders = cloned.propagate_to_loaders 

548 

549 @_generative 

550 def set_column_strategy(self, attrs, strategy, opts=None, opts_only=False): 

551 strategy = self._coerce_strat(strategy) 

552 self.is_class_strategy = False 

553 for attr in attrs: 

554 cloned = self._clone_for_bind_strategy( 

555 attr, strategy, "column", opts_only=opts_only, opts=opts 

556 ) 

557 cloned.propagate_to_loaders = True 

558 

559 @_generative 

560 def set_generic_strategy(self, attrs, strategy): 

561 strategy = self._coerce_strat(strategy) 

562 for attr in attrs: 

563 cloned = self._clone_for_bind_strategy(attr, strategy, None) 

564 cloned.propagate_to_loaders = True 

565 

566 @_generative 

567 def set_class_strategy(self, strategy, opts): 

568 strategy = self._coerce_strat(strategy) 

569 cloned = self._clone_for_bind_strategy(None, strategy, None) 

570 cloned.is_class_strategy = True 

571 cloned.propagate_to_loaders = True 

572 cloned.local_opts.update(opts) 

573 

574 def _clone_for_bind_strategy( 

575 self, attr, strategy, wildcard_key, opts_only=False, opts=None 

576 ): 

577 """Create an anonymous clone of the Load/_UnboundLoad that is suitable 

578 to be placed in the context / _to_bind collection of this Load 

579 object. The clone will then lose references to context/_to_bind 

580 in order to not create reference cycles. 

581 

582 """ 

583 cloned = self._generate() 

584 cloned._generate_path(self.path, attr, strategy, wildcard_key) 

585 cloned.strategy = strategy 

586 

587 cloned.local_opts = self.local_opts 

588 if opts: 

589 cloned.local_opts.update(opts) 

590 if opts_only: 

591 cloned.is_opts_only = True 

592 

593 if strategy or cloned.is_opts_only: 

594 cloned._set_path_strategy() 

595 return cloned 

596 

597 def _set_for_path(self, context, path, replace=True, merge_opts=False): 

598 if merge_opts or not replace: 

599 existing = path.get(context, "loader") 

600 if existing: 

601 if merge_opts: 

602 existing.local_opts.update(self.local_opts) 

603 existing._extra_criteria += self._extra_criteria 

604 else: 

605 path.set(context, "loader", self) 

606 else: 

607 existing = path.get(context, "loader") 

608 path.set(context, "loader", self) 

609 if existing and existing.is_opts_only: 

610 self.local_opts.update(existing.local_opts) 

611 existing._extra_criteria += self._extra_criteria 

612 

613 def _set_path_strategy(self): 

614 if not self.is_class_strategy and self.path.has_entity: 

615 effective_path = self.path.parent 

616 else: 

617 effective_path = self.path 

618 

619 if effective_path.is_token: 

620 for path in effective_path.generate_for_superclasses(): 

621 self._set_for_path( 

622 self.context, 

623 path, 

624 replace=True, 

625 merge_opts=self.is_opts_only, 

626 ) 

627 else: 

628 self._set_for_path( 

629 self.context, 

630 effective_path, 

631 replace=True, 

632 merge_opts=self.is_opts_only, 

633 ) 

634 

635 # remove cycles; _set_path_strategy is always invoked on an 

636 # anonymous clone of the Load / UnboundLoad object since #5056 

637 self.context = None 

638 

639 def __getstate__(self): 

640 d = self.__dict__.copy() 

641 

642 # can't pickle this right now; warning is raised by strategies 

643 d["_extra_criteria"] = () 

644 

645 if d["context"] is not None: 

646 d["context"] = PathRegistry.serialize_context_dict( 

647 d["context"], ("loader",) 

648 ) 

649 d["path"] = self.path.serialize() 

650 return d 

651 

652 def __setstate__(self, state): 

653 self.__dict__.update(state) 

654 self.path = PathRegistry.deserialize(self.path) 

655 if self.context is not None: 

656 self.context = PathRegistry.deserialize_context_dict(self.context) 

657 

658 def _chop_path(self, to_chop, path): 

659 i = -1 

660 

661 for i, (c_token, p_token) in enumerate(zip(to_chop, path.path)): 

662 if isinstance(c_token, util.string_types): 

663 # TODO: this is approximated from the _UnboundLoad 

664 # version and probably has issues, not fully covered. 

665 

666 if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN): 

667 return to_chop 

668 elif ( 

669 c_token != "relationship:%s" % (_WILDCARD_TOKEN,) 

670 and c_token != p_token.key 

671 ): 

672 return None 

673 

674 if c_token is p_token: 

675 continue 

676 elif ( 

677 isinstance(c_token, InspectionAttr) 

678 and c_token.is_mapper 

679 and p_token.is_mapper 

680 and c_token.isa(p_token) 

681 ): 

682 continue 

683 else: 

684 return None 

685 return to_chop[i + 1 :] 

686 

687 

688class _UnboundLoad(Load): 

689 """Represent a loader option that isn't tied to a root entity. 

690 

691 The loader option will produce an entity-linked :class:`_orm.Load` 

692 object when it is passed :meth:`_query.Query.options`. 

693 

694 This provides compatibility with the traditional system 

695 of freestanding options, e.g. ``joinedload('x.y.z')``. 

696 

697 """ 

698 

699 def __init__(self): 

700 self.path = () 

701 self._to_bind = [] 

702 self.local_opts = {} 

703 self._extra_criteria = () 

704 

705 def _gen_cache_key(self, anon_map, bindparams, _unbound_option_seen=None): 

706 """Inlined gen_cache_key 

707 

708 Original traversal is:: 

709 

710 

711 _cache_key_traversal = [ 

712 ("path", visitors.ExtendedInternalTraversal.dp_multi_list), 

713 ("strategy", visitors.ExtendedInternalTraversal.dp_plain_obj), 

714 ( 

715 "_to_bind", 

716 visitors.ExtendedInternalTraversal.dp_has_cache_key_list, 

717 ), 

718 ( 

719 "_extra_criteria", 

720 visitors.InternalTraversal.dp_clauseelement_list), 

721 ( 

722 "local_opts", 

723 visitors.ExtendedInternalTraversal.dp_string_multi_dict, 

724 ), 

725 ] 

726 

727 The inlining is so that the "_to_bind" list can be flattened to not 

728 repeat the same UnboundLoad options over and over again. 

729 

730 See #6869 

731 

732 """ 

733 

734 idself = id(self) 

735 cls = self.__class__ 

736 

737 if idself in anon_map: 

738 return (anon_map[idself], cls) 

739 else: 

740 id_ = anon_map[idself] 

741 

742 vis = traversals._cache_key_traversal_visitor 

743 

744 seen = _unbound_option_seen 

745 if seen is None: 

746 seen = set() 

747 

748 return ( 

749 (id_, cls) 

750 + vis.visit_multi_list( 

751 "path", self.path, self, anon_map, bindparams 

752 ) 

753 + ("strategy", self.strategy) 

754 + ( 

755 ( 

756 "_to_bind", 

757 tuple( 

758 elem._gen_cache_key( 

759 anon_map, bindparams, _unbound_option_seen=seen 

760 ) 

761 for elem in self._to_bind 

762 if elem not in seen and not seen.add(elem) 

763 ), 

764 ) 

765 if self._to_bind 

766 else () 

767 ) 

768 + ( 

769 ( 

770 "_extra_criteria", 

771 tuple( 

772 elem._gen_cache_key(anon_map, bindparams) 

773 for elem in self._extra_criteria 

774 ), 

775 ) 

776 if self._extra_criteria 

777 else () 

778 ) 

779 + ( 

780 vis.visit_string_multi_dict( 

781 "local_opts", self.local_opts, self, anon_map, bindparams 

782 ) 

783 if self.local_opts 

784 else () 

785 ) 

786 ) 

787 

788 _is_chain_link = False 

789 

790 def _set_path_strategy(self): 

791 self._to_bind.append(self) 

792 

793 # remove cycles; _set_path_strategy is always invoked on an 

794 # anonymous clone of the Load / UnboundLoad object since #5056 

795 self._to_bind = None 

796 

797 def _deep_clone(self, applied, process): 

798 if self in applied: 

799 return applied[self] 

800 

801 cloned = self._generate() 

802 

803 applied[self] = cloned 

804 

805 cloned.strategy = self.strategy 

806 

807 assert cloned.propagate_to_loaders == self.propagate_to_loaders 

808 assert cloned.is_class_strategy == self.is_class_strategy 

809 assert cloned.is_opts_only == self.is_opts_only 

810 

811 cloned._to_bind = [ 

812 elem._deep_clone(applied, process) for elem in self._to_bind or () 

813 ] 

814 

815 cloned.local_opts.update(self.local_opts) 

816 

817 process(cloned) 

818 

819 return cloned 

820 

821 def _apply_to_parent(self, parent, applied, bound, to_bind=None): 

822 if self in applied: 

823 return applied[self] 

824 

825 if to_bind is None: 

826 to_bind = self._to_bind 

827 

828 cloned = self._generate() 

829 

830 applied[self] = cloned 

831 

832 cloned.strategy = self.strategy 

833 if self.path: 

834 attr = self.path[-1] 

835 if isinstance(attr, util.string_types) and attr.endswith( 

836 _DEFAULT_TOKEN 

837 ): 

838 attr = attr.split(":")[0] + ":" + _WILDCARD_TOKEN 

839 cloned._generate_path( 

840 parent.path + self.path[0:-1], attr, self.strategy, None 

841 ) 

842 

843 # these assertions can go away once the "sub options" API is 

844 # mature 

845 assert cloned.propagate_to_loaders == self.propagate_to_loaders 

846 assert cloned.is_class_strategy == self.is_class_strategy 

847 assert cloned.is_opts_only == self.is_opts_only 

848 

849 uniq = set() 

850 

851 cloned._to_bind = parent._to_bind 

852 

853 cloned._to_bind[:] = [ 

854 elem 

855 for elem in cloned._to_bind 

856 if elem not in uniq and not uniq.add(elem) 

857 ] + [ 

858 elem._apply_to_parent(parent, applied, bound, to_bind) 

859 for elem in to_bind 

860 if elem not in uniq and not uniq.add(elem) 

861 ] 

862 

863 cloned.local_opts.update(self.local_opts) 

864 

865 return cloned 

866 

867 def _generate_path(self, path, attr, for_strategy, wildcard_key): 

868 if ( 

869 wildcard_key 

870 and isinstance(attr, util.string_types) 

871 and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN) 

872 ): 

873 if attr == _DEFAULT_TOKEN: 

874 self.propagate_to_loaders = False 

875 attr = "%s:%s" % (wildcard_key, attr) 

876 if path and _is_mapped_class(path[-1]) and not self.is_class_strategy: 

877 path = path[0:-1] 

878 if attr: 

879 path = path + (attr,) 

880 self.path = path 

881 self._extra_criteria = getattr(attr, "_extra_criteria", ()) 

882 

883 return path 

884 

885 def __getstate__(self): 

886 d = self.__dict__.copy() 

887 

888 # can't pickle this right now; warning is raised by strategies 

889 d["_extra_criteria"] = () 

890 

891 d["path"] = self._serialize_path(self.path, filter_aliased_class=True) 

892 return d 

893 

894 def __setstate__(self, state): 

895 ret = [] 

896 for key in state["path"]: 

897 if isinstance(key, tuple): 

898 if len(key) == 2: 

899 # support legacy 

900 cls, propkey = key 

901 of_type = None 

902 else: 

903 cls, propkey, of_type = key 

904 prop = getattr(cls, propkey) 

905 if of_type: 

906 prop = prop.of_type(of_type) 

907 ret.append(prop) 

908 else: 

909 ret.append(key) 

910 state["path"] = tuple(ret) 

911 self.__dict__ = state 

912 

913 def _process(self, compile_state, mapper_entities, raiseerr): 

914 dedupes = compile_state.attributes["_unbound_load_dedupes"] 

915 is_refresh = compile_state.compile_options._for_refresh_state 

916 for val in self._to_bind: 

917 if val not in dedupes: 

918 dedupes.add(val) 

919 if is_refresh and not val.propagate_to_loaders: 

920 continue 

921 val._bind_loader( 

922 [ent.entity_zero for ent in mapper_entities], 

923 compile_state.current_path, 

924 compile_state.attributes, 

925 raiseerr, 

926 ) 

927 

928 @classmethod 

929 def _from_keys(cls, meth, keys, chained, kw): 

930 opt = _UnboundLoad() 

931 

932 def _split_key(key): 

933 if isinstance(key, util.string_types): 

934 # coerce fooload('*') into "default loader strategy" 

935 if key == _WILDCARD_TOKEN: 

936 return (_DEFAULT_TOKEN,) 

937 # coerce fooload(".*") into "wildcard on default entity" 

938 elif key.startswith("." + _WILDCARD_TOKEN): 

939 util.warn_deprecated( 

940 "The undocumented `.{WILDCARD}` format is deprecated " 

941 "and will be removed in a future version as it is " 

942 "believed to be unused. " 

943 "If you have been using this functionality, please " 

944 "comment on Issue #4390 on the SQLAlchemy project " 

945 "tracker.", 

946 version="1.4", 

947 ) 

948 key = key[1:] 

949 return key.split(".") 

950 else: 

951 return (key,) 

952 

953 all_tokens = [token for key in keys for token in _split_key(key)] 

954 

955 for token in all_tokens[0:-1]: 

956 # set _is_chain_link first so that clones of the 

957 # object also inherit this flag 

958 opt._is_chain_link = True 

959 if chained: 

960 opt = meth(opt, token, **kw) 

961 else: 

962 opt = opt.defaultload(token) 

963 

964 opt = meth(opt, all_tokens[-1], **kw) 

965 opt._is_chain_link = False 

966 return opt 

967 

968 def _chop_path(self, to_chop, path): 

969 i = -1 

970 for i, (c_token, (p_entity, p_prop)) in enumerate( 

971 zip(to_chop, path.pairs()) 

972 ): 

973 if isinstance(c_token, util.string_types): 

974 if i == 0 and c_token.endswith(":" + _DEFAULT_TOKEN): 

975 return to_chop 

976 elif ( 

977 c_token != "relationship:%s" % (_WILDCARD_TOKEN,) 

978 and c_token != p_prop.key 

979 ): 

980 return None 

981 elif isinstance(c_token, PropComparator): 

982 if c_token.property is not p_prop or ( 

983 c_token._parententity is not p_entity 

984 and ( 

985 not c_token._parententity.is_mapper 

986 or not c_token._parententity.isa(p_entity) 

987 ) 

988 ): 

989 return None 

990 else: 

991 i += 1 

992 

993 return to_chop[i:] 

994 

995 def _serialize_path(self, path, filter_aliased_class=False): 

996 ret = [] 

997 for token in path: 

998 if isinstance(token, QueryableAttribute): 

999 if ( 

1000 filter_aliased_class 

1001 and token._of_type 

1002 and inspect(token._of_type).is_aliased_class 

1003 ): 

1004 ret.append((token._parentmapper.class_, token.key, None)) 

1005 else: 

1006 ret.append( 

1007 ( 

1008 token._parentmapper.class_, 

1009 token.key, 

1010 token._of_type.entity if token._of_type else None, 

1011 ) 

1012 ) 

1013 elif isinstance(token, PropComparator): 

1014 ret.append((token._parentmapper.class_, token.key, None)) 

1015 else: 

1016 ret.append(token) 

1017 return ret 

1018 

1019 def _bind_loader(self, entities, current_path, context, raiseerr): 

1020 """Convert from an _UnboundLoad() object into a Load() object. 

1021 

1022 The _UnboundLoad() uses an informal "path" and does not necessarily 

1023 refer to a lead entity as it may use string tokens. The Load() 

1024 OTOH refers to a complete path. This method reconciles from a 

1025 given Query into a Load. 

1026 

1027 Example:: 

1028 

1029 

1030 query = session.query(User).options( 

1031 joinedload("orders").joinedload("items")) 

1032 

1033 The above options will be an _UnboundLoad object along the lines 

1034 of (note this is not the exact API of _UnboundLoad):: 

1035 

1036 _UnboundLoad( 

1037 _to_bind=[ 

1038 _UnboundLoad(["orders"], {"lazy": "joined"}), 

1039 _UnboundLoad(["orders", "items"], {"lazy": "joined"}), 

1040 ] 

1041 ) 

1042 

1043 After this method, we get something more like this (again this is 

1044 not exact API):: 

1045 

1046 Load( 

1047 User, 

1048 (User, User.orders.property)) 

1049 Load( 

1050 User, 

1051 (User, User.orders.property, Order, Order.items.property)) 

1052 

1053 """ 

1054 

1055 start_path = self.path 

1056 

1057 if self.is_class_strategy and current_path: 

1058 start_path += (entities[0],) 

1059 

1060 # _current_path implies we're in a 

1061 # secondary load with an existing path 

1062 

1063 if current_path: 

1064 start_path = self._chop_path(start_path, current_path) 

1065 

1066 if not start_path: 

1067 return None 

1068 

1069 # look at the first token and try to locate within the Query 

1070 # what entity we are referring towards. 

1071 token = start_path[0] 

1072 

1073 if isinstance(token, util.string_types): 

1074 entity = self._find_entity_basestring(entities, token, raiseerr) 

1075 elif isinstance(token, PropComparator): 

1076 prop = token.property 

1077 entity = self._find_entity_prop_comparator( 

1078 entities, prop, token._parententity, raiseerr 

1079 ) 

1080 elif self.is_class_strategy and _is_mapped_class(token): 

1081 entity = inspect(token) 

1082 if entity not in entities: 

1083 entity = None 

1084 else: 

1085 raise sa_exc.ArgumentError( 

1086 "mapper option expects " "string key or list of attributes" 

1087 ) 

1088 

1089 if not entity: 

1090 return 

1091 

1092 path_element = entity 

1093 

1094 # transfer our entity-less state into a Load() object 

1095 # with a real entity path. Start with the lead entity 

1096 # we just located, then go through the rest of our path 

1097 # tokens and populate into the Load(). 

1098 loader = Load(path_element) 

1099 

1100 if context is None: 

1101 context = loader.context 

1102 

1103 loader.strategy = self.strategy 

1104 loader.is_opts_only = self.is_opts_only 

1105 loader.is_class_strategy = self.is_class_strategy 

1106 loader._extra_criteria = self._extra_criteria 

1107 

1108 path = loader.path 

1109 

1110 if not loader.is_class_strategy: 

1111 for idx, token in enumerate(start_path): 

1112 if not loader._generate_path( 

1113 loader.path, 

1114 token, 

1115 self.strategy if idx == len(start_path) - 1 else None, 

1116 None, 

1117 raiseerr, 

1118 polymorphic_entity_context=context, 

1119 ): 

1120 return 

1121 

1122 loader.local_opts.update(self.local_opts) 

1123 

1124 if not loader.is_class_strategy and loader.path.has_entity: 

1125 effective_path = loader.path.parent 

1126 else: 

1127 effective_path = loader.path 

1128 

1129 # prioritize "first class" options over those 

1130 # that were "links in the chain", e.g. "x" and "y" in 

1131 # someload("x.y.z") versus someload("x") / someload("x.y") 

1132 

1133 if effective_path.is_token: 

1134 for path in effective_path.generate_for_superclasses(): 

1135 loader._set_for_path( 

1136 context, 

1137 path, 

1138 replace=not self._is_chain_link, 

1139 merge_opts=self.is_opts_only, 

1140 ) 

1141 else: 

1142 loader._set_for_path( 

1143 context, 

1144 effective_path, 

1145 replace=not self._is_chain_link, 

1146 merge_opts=self.is_opts_only, 

1147 ) 

1148 

1149 return loader 

1150 

1151 def _find_entity_prop_comparator(self, entities, prop, mapper, raiseerr): 

1152 if _is_aliased_class(mapper): 

1153 searchfor = mapper 

1154 else: 

1155 searchfor = _class_to_mapper(mapper) 

1156 for ent in entities: 

1157 if orm_util._entity_corresponds_to(ent, searchfor): 

1158 return ent 

1159 else: 

1160 if raiseerr: 

1161 if not list(entities): 

1162 raise sa_exc.ArgumentError( 

1163 "Query has only expression-based entities, " 

1164 'which do not apply to %s "%s"' 

1165 % (util.clsname_as_plain_name(type(prop)), prop) 

1166 ) 

1167 else: 

1168 raise sa_exc.ArgumentError( 

1169 'Mapped attribute "%s" does not apply to any of the ' 

1170 "root entities in this query, e.g. %s. Please " 

1171 "specify the full path " 

1172 "from one of the root entities to the target " 

1173 "attribute. " 

1174 % (prop, ", ".join(str(x) for x in entities)) 

1175 ) 

1176 else: 

1177 return None 

1178 

1179 def _find_entity_basestring(self, entities, token, raiseerr): 

1180 if token.endswith(":" + _WILDCARD_TOKEN): 

1181 if len(list(entities)) != 1: 

1182 if raiseerr: 

1183 raise sa_exc.ArgumentError( 

1184 "Can't apply wildcard ('*') or load_only() " 

1185 "loader option to multiple entities %s. Specify " 

1186 "loader options for each entity individually, such " 

1187 "as %s." 

1188 % ( 

1189 ", ".join(str(ent) for ent in entities), 

1190 ", ".join( 

1191 "Load(%s).some_option('*')" % ent 

1192 for ent in entities 

1193 ), 

1194 ) 

1195 ) 

1196 elif token.endswith(_DEFAULT_TOKEN): 

1197 raiseerr = False 

1198 

1199 for ent in entities: 

1200 # return only the first _MapperEntity when searching 

1201 # based on string prop name. Ideally object 

1202 # attributes are used to specify more exactly. 

1203 return ent 

1204 else: 

1205 if raiseerr: 

1206 raise sa_exc.ArgumentError( 

1207 "Query has only expression-based entities - " 

1208 'can\'t find property named "%s".' % (token,) 

1209 ) 

1210 else: 

1211 return None 

1212 

1213 

1214class loader_option(object): 

1215 def __init__(self): 

1216 pass 

1217 

1218 def __call__(self, fn): 

1219 self.name = name = fn.__name__ 

1220 self.fn = fn 

1221 if hasattr(Load, name): 

1222 raise TypeError("Load class already has a %s method." % (name)) 

1223 setattr(Load, name, fn) 

1224 

1225 return self 

1226 

1227 def _add_unbound_fn(self, fn): 

1228 self._unbound_fn = fn 

1229 fn_doc = self.fn.__doc__ 

1230 self.fn.__doc__ = """Produce a new :class:`_orm.Load` object with the 

1231:func:`_orm.%(name)s` option applied. 

1232 

1233See :func:`_orm.%(name)s` for usage examples. 

1234 

1235""" % { 

1236 "name": self.name 

1237 } 

1238 

1239 fn.__doc__ = fn_doc 

1240 return self 

1241 

1242 def _add_unbound_all_fn(self, fn): 

1243 fn.__doc__ = """Produce a standalone "all" option for 

1244:func:`_orm.%(name)s`. 

1245 

1246.. deprecated:: 0.9 

1247 

1248 The :func:`_orm.%(name)s_all` function is deprecated, and will be removed 

1249 in a future release. Please use method chaining with 

1250 :func:`_orm.%(name)s` instead, as in:: 

1251 

1252 session.query(MyClass).options( 

1253 %(name)s("someattribute").%(name)s("anotherattribute") 

1254 ) 

1255 

1256""" % { 

1257 "name": self.name 

1258 } 

1259 fn = util.deprecated( 

1260 # This is used by `baked_lazyload_all` was only deprecated in 

1261 # version 1.2 so this must stick around until that is removed 

1262 "0.9", 

1263 "The :func:`.%(name)s_all` function is deprecated, and will be " 

1264 "removed in a future release. Please use method chaining with " 

1265 ":func:`.%(name)s` instead" % {"name": self.name}, 

1266 add_deprecation_to_docstring=False, 

1267 )(fn) 

1268 

1269 self._unbound_all_fn = fn 

1270 return self 

1271 

1272 

1273@loader_option() 

1274def contains_eager(loadopt, attr, alias=None): 

1275 r"""Indicate that the given attribute should be eagerly loaded from 

1276 columns stated manually in the query. 

1277 

1278 This function is part of the :class:`_orm.Load` interface and supports 

1279 both method-chained and standalone operation. 

1280 

1281 The option is used in conjunction with an explicit join that loads 

1282 the desired rows, i.e.:: 

1283 

1284 sess.query(Order).\ 

1285 join(Order.user).\ 

1286 options(contains_eager(Order.user)) 

1287 

1288 The above query would join from the ``Order`` entity to its related 

1289 ``User`` entity, and the returned ``Order`` objects would have the 

1290 ``Order.user`` attribute pre-populated. 

1291 

1292 It may also be used for customizing the entries in an eagerly loaded 

1293 collection; queries will normally want to use the 

1294 :meth:`_query.Query.populate_existing` method assuming the primary 

1295 collection of parent objects may already have been loaded:: 

1296 

1297 sess.query(User).\ 

1298 join(User.addresses).\ 

1299 filter(Address.email_address.like('%@aol.com')).\ 

1300 options(contains_eager(User.addresses)).\ 

1301 populate_existing() 

1302 

1303 See the section :ref:`contains_eager` for complete usage details. 

1304 

1305 .. seealso:: 

1306 

1307 :ref:`loading_toplevel` 

1308 

1309 :ref:`contains_eager` 

1310 

1311 """ 

1312 if alias is not None: 

1313 if not isinstance(alias, str): 

1314 info = inspect(alias) 

1315 alias = info.selectable 

1316 

1317 else: 

1318 util.warn_deprecated( 

1319 "Passing a string name for the 'alias' argument to " 

1320 "'contains_eager()` is deprecated, and will not work in a " 

1321 "future release. Please use a sqlalchemy.alias() or " 

1322 "sqlalchemy.orm.aliased() construct.", 

1323 version="1.4", 

1324 ) 

1325 

1326 elif getattr(attr, "_of_type", None): 

1327 ot = inspect(attr._of_type) 

1328 alias = ot.selectable 

1329 

1330 cloned = loadopt.set_relationship_strategy( 

1331 attr, {"lazy": "joined"}, propagate_to_loaders=False 

1332 ) 

1333 cloned.local_opts["eager_from_alias"] = alias 

1334 return cloned 

1335 

1336 

1337@contains_eager._add_unbound_fn 

1338def contains_eager(*keys, **kw): 

1339 return _UnboundLoad()._from_keys( 

1340 _UnboundLoad.contains_eager, keys, True, kw 

1341 ) 

1342 

1343 

1344@loader_option() 

1345def load_only(loadopt, *attrs): 

1346 """Indicate that for a particular entity, only the given list 

1347 of column-based attribute names should be loaded; all others will be 

1348 deferred. 

1349 

1350 This function is part of the :class:`_orm.Load` interface and supports 

1351 both method-chained and standalone operation. 

1352 

1353 Example - given a class ``User``, load only the ``name`` and ``fullname`` 

1354 attributes:: 

1355 

1356 session.query(User).options(load_only(User.name, User.fullname)) 

1357 

1358 Example - given a relationship ``User.addresses -> Address``, specify 

1359 subquery loading for the ``User.addresses`` collection, but on each 

1360 ``Address`` object load only the ``email_address`` attribute:: 

1361 

1362 session.query(User).options( 

1363 subqueryload(User.addresses).load_only(Address.email_address) 

1364 ) 

1365 

1366 For a :class:`_query.Query` that has multiple entities, 

1367 the lead entity can be 

1368 specifically referred to using the :class:`_orm.Load` constructor:: 

1369 

1370 session.query(User, Address).join(User.addresses).options( 

1371 Load(User).load_only(User.name, User.fullname), 

1372 Load(Address).load_only(Address.email_address) 

1373 ) 

1374 

1375 .. note:: This method will still load a :class:`_schema.Column` even 

1376 if the column property is defined with ``deferred=True`` 

1377 for the :func:`.column_property` function. 

1378 

1379 .. versionadded:: 0.9.0 

1380 

1381 """ 

1382 cloned = loadopt.set_column_strategy( 

1383 attrs, {"deferred": False, "instrument": True} 

1384 ) 

1385 cloned.set_column_strategy( 

1386 "*", {"deferred": True, "instrument": True}, {"undefer_pks": True} 

1387 ) 

1388 return cloned 

1389 

1390 

1391@load_only._add_unbound_fn 

1392def load_only(*attrs): 

1393 return _UnboundLoad().load_only(*attrs) 

1394 

1395 

1396@loader_option() 

1397def joinedload(loadopt, attr, innerjoin=None): 

1398 """Indicate that the given attribute should be loaded using joined 

1399 eager loading. 

1400 

1401 This function is part of the :class:`_orm.Load` interface and supports 

1402 both method-chained and standalone operation. 

1403 

1404 examples:: 

1405 

1406 # joined-load the "orders" collection on "User" 

1407 query(User).options(joinedload(User.orders)) 

1408 

1409 # joined-load Order.items and then Item.keywords 

1410 query(Order).options( 

1411 joinedload(Order.items).joinedload(Item.keywords)) 

1412 

1413 # lazily load Order.items, but when Items are loaded, 

1414 # joined-load the keywords collection 

1415 query(Order).options( 

1416 lazyload(Order.items).joinedload(Item.keywords)) 

1417 

1418 :param innerjoin: if ``True``, indicates that the joined eager load should 

1419 use an inner join instead of the default of left outer join:: 

1420 

1421 query(Order).options(joinedload(Order.user, innerjoin=True)) 

1422 

1423 In order to chain multiple eager joins together where some may be 

1424 OUTER and others INNER, right-nested joins are used to link them:: 

1425 

1426 query(A).options( 

1427 joinedload(A.bs, innerjoin=False). 

1428 joinedload(B.cs, innerjoin=True) 

1429 ) 

1430 

1431 The above query, linking A.bs via "outer" join and B.cs via "inner" join 

1432 would render the joins as "a LEFT OUTER JOIN (b JOIN c)". When using 

1433 older versions of SQLite (< 3.7.16), this form of JOIN is translated to 

1434 use full subqueries as this syntax is otherwise not directly supported. 

1435 

1436 The ``innerjoin`` flag can also be stated with the term ``"unnested"``. 

1437 This indicates that an INNER JOIN should be used, *unless* the join 

1438 is linked to a LEFT OUTER JOIN to the left, in which case it 

1439 will render as LEFT OUTER JOIN. For example, supposing ``A.bs`` 

1440 is an outerjoin:: 

1441 

1442 query(A).options( 

1443 joinedload(A.bs). 

1444 joinedload(B.cs, innerjoin="unnested") 

1445 ) 

1446 

1447 The above join will render as "a LEFT OUTER JOIN b LEFT OUTER JOIN c", 

1448 rather than as "a LEFT OUTER JOIN (b JOIN c)". 

1449 

1450 .. note:: The "unnested" flag does **not** affect the JOIN rendered 

1451 from a many-to-many association table, e.g. a table configured 

1452 as :paramref:`_orm.relationship.secondary`, to the target table; for 

1453 correctness of results, these joins are always INNER and are 

1454 therefore right-nested if linked to an OUTER join. 

1455 

1456 .. versionchanged:: 1.0.0 ``innerjoin=True`` now implies 

1457 ``innerjoin="nested"``, whereas in 0.9 it implied 

1458 ``innerjoin="unnested"``. In order to achieve the pre-1.0 "unnested" 

1459 inner join behavior, use the value ``innerjoin="unnested"``. 

1460 See :ref:`migration_3008`. 

1461 

1462 .. note:: 

1463 

1464 The joins produced by :func:`_orm.joinedload` are **anonymously 

1465 aliased**. The criteria by which the join proceeds cannot be 

1466 modified, nor can the :class:`_query.Query` 

1467 refer to these joins in any way, 

1468 including ordering. See :ref:`zen_of_eager_loading` for further 

1469 detail. 

1470 

1471 To produce a specific SQL JOIN which is explicitly available, use 

1472 :meth:`_query.Query.join`. 

1473 To combine explicit JOINs with eager loading 

1474 of collections, use :func:`_orm.contains_eager`; see 

1475 :ref:`contains_eager`. 

1476 

1477 .. seealso:: 

1478 

1479 :ref:`loading_toplevel` 

1480 

1481 :ref:`joined_eager_loading` 

1482 

1483 """ 

1484 loader = loadopt.set_relationship_strategy(attr, {"lazy": "joined"}) 

1485 if innerjoin is not None: 

1486 loader.local_opts["innerjoin"] = innerjoin 

1487 return loader 

1488 

1489 

1490@joinedload._add_unbound_fn 

1491def joinedload(*keys, **kw): 

1492 return _UnboundLoad._from_keys(_UnboundLoad.joinedload, keys, False, kw) 

1493 

1494 

1495@loader_option() 

1496def subqueryload(loadopt, attr): 

1497 """Indicate that the given attribute should be loaded using 

1498 subquery eager loading. 

1499 

1500 This function is part of the :class:`_orm.Load` interface and supports 

1501 both method-chained and standalone operation. 

1502 

1503 examples:: 

1504 

1505 # subquery-load the "orders" collection on "User" 

1506 query(User).options(subqueryload(User.orders)) 

1507 

1508 # subquery-load Order.items and then Item.keywords 

1509 query(Order).options( 

1510 subqueryload(Order.items).subqueryload(Item.keywords)) 

1511 

1512 # lazily load Order.items, but when Items are loaded, 

1513 # subquery-load the keywords collection 

1514 query(Order).options( 

1515 lazyload(Order.items).subqueryload(Item.keywords)) 

1516 

1517 

1518 .. seealso:: 

1519 

1520 :ref:`loading_toplevel` 

1521 

1522 :ref:`subquery_eager_loading` 

1523 

1524 """ 

1525 return loadopt.set_relationship_strategy(attr, {"lazy": "subquery"}) 

1526 

1527 

1528@subqueryload._add_unbound_fn 

1529def subqueryload(*keys): 

1530 return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, False, {}) 

1531 

1532 

1533@loader_option() 

1534def selectinload(loadopt, attr): 

1535 """Indicate that the given attribute should be loaded using 

1536 SELECT IN eager loading. 

1537 

1538 This function is part of the :class:`_orm.Load` interface and supports 

1539 both method-chained and standalone operation. 

1540 

1541 examples:: 

1542 

1543 # selectin-load the "orders" collection on "User" 

1544 query(User).options(selectinload(User.orders)) 

1545 

1546 # selectin-load Order.items and then Item.keywords 

1547 query(Order).options( 

1548 selectinload(Order.items).selectinload(Item.keywords)) 

1549 

1550 # lazily load Order.items, but when Items are loaded, 

1551 # selectin-load the keywords collection 

1552 query(Order).options( 

1553 lazyload(Order.items).selectinload(Item.keywords)) 

1554 

1555 .. versionadded:: 1.2 

1556 

1557 .. seealso:: 

1558 

1559 :ref:`loading_toplevel` 

1560 

1561 :ref:`selectin_eager_loading` 

1562 

1563 """ 

1564 return loadopt.set_relationship_strategy(attr, {"lazy": "selectin"}) 

1565 

1566 

1567@selectinload._add_unbound_fn 

1568def selectinload(*keys): 

1569 return _UnboundLoad._from_keys(_UnboundLoad.selectinload, keys, False, {}) 

1570 

1571 

1572@loader_option() 

1573def lazyload(loadopt, attr): 

1574 """Indicate that the given attribute should be loaded using "lazy" 

1575 loading. 

1576 

1577 This function is part of the :class:`_orm.Load` interface and supports 

1578 both method-chained and standalone operation. 

1579 

1580 .. seealso:: 

1581 

1582 :ref:`loading_toplevel` 

1583 

1584 :ref:`lazy_loading` 

1585 

1586 """ 

1587 return loadopt.set_relationship_strategy(attr, {"lazy": "select"}) 

1588 

1589 

1590@lazyload._add_unbound_fn 

1591def lazyload(*keys): 

1592 return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, False, {}) 

1593 

1594 

1595@loader_option() 

1596def immediateload(loadopt, attr): 

1597 """Indicate that the given attribute should be loaded using 

1598 an immediate load with a per-attribute SELECT statement. 

1599 

1600 The load is achieved using the "lazyloader" strategy and does not 

1601 fire off any additional eager loaders. 

1602 

1603 The :func:`.immediateload` option is superseded in general 

1604 by the :func:`.selectinload` option, which performs the same task 

1605 more efficiently by emitting a SELECT for all loaded objects. 

1606 

1607 This function is part of the :class:`_orm.Load` interface and supports 

1608 both method-chained and standalone operation. 

1609 

1610 .. seealso:: 

1611 

1612 :ref:`loading_toplevel` 

1613 

1614 :ref:`selectin_eager_loading` 

1615 

1616 """ 

1617 loader = loadopt.set_relationship_strategy(attr, {"lazy": "immediate"}) 

1618 return loader 

1619 

1620 

1621@immediateload._add_unbound_fn 

1622def immediateload(*keys): 

1623 return _UnboundLoad._from_keys(_UnboundLoad.immediateload, keys, False, {}) 

1624 

1625 

1626@loader_option() 

1627def noload(loadopt, attr): 

1628 """Indicate that the given relationship attribute should remain unloaded. 

1629 

1630 The relationship attribute will return ``None`` when accessed without 

1631 producing any loading effect. 

1632 

1633 This function is part of the :class:`_orm.Load` interface and supports 

1634 both method-chained and standalone operation. 

1635 

1636 :func:`_orm.noload` applies to :func:`_orm.relationship` attributes; for 

1637 column-based attributes, see :func:`_orm.defer`. 

1638 

1639 .. note:: Setting this loading strategy as the default strategy 

1640 for a relationship using the :paramref:`.orm.relationship.lazy` 

1641 parameter may cause issues with flushes, such if a delete operation 

1642 needs to load related objects and instead ``None`` was returned. 

1643 

1644 .. seealso:: 

1645 

1646 :ref:`loading_toplevel` 

1647 

1648 """ 

1649 

1650 return loadopt.set_relationship_strategy(attr, {"lazy": "noload"}) 

1651 

1652 

1653@noload._add_unbound_fn 

1654def noload(*keys): 

1655 return _UnboundLoad._from_keys(_UnboundLoad.noload, keys, False, {}) 

1656 

1657 

1658@loader_option() 

1659def raiseload(loadopt, attr, sql_only=False): 

1660 """Indicate that the given attribute should raise an error if accessed. 

1661 

1662 A relationship attribute configured with :func:`_orm.raiseload` will 

1663 raise an :exc:`~sqlalchemy.exc.InvalidRequestError` upon access. The 

1664 typical way this is useful is when an application is attempting to ensure 

1665 that all relationship attributes that are accessed in a particular context 

1666 would have been already loaded via eager loading. Instead of having 

1667 to read through SQL logs to ensure lazy loads aren't occurring, this 

1668 strategy will cause them to raise immediately. 

1669 

1670 :func:`_orm.raiseload` applies to :func:`_orm.relationship` 

1671 attributes only. 

1672 In order to apply raise-on-SQL behavior to a column-based attribute, 

1673 use the :paramref:`.orm.defer.raiseload` parameter on the :func:`.defer` 

1674 loader option. 

1675 

1676 :param sql_only: if True, raise only if the lazy load would emit SQL, but 

1677 not if it is only checking the identity map, or determining that the 

1678 related value should just be None due to missing keys. When False, the 

1679 strategy will raise for all varieties of relationship loading. 

1680 

1681 This function is part of the :class:`_orm.Load` interface and supports 

1682 both method-chained and standalone operation. 

1683 

1684 

1685 .. versionadded:: 1.1 

1686 

1687 .. seealso:: 

1688 

1689 :ref:`loading_toplevel` 

1690 

1691 :ref:`prevent_lazy_with_raiseload` 

1692 

1693 :ref:`deferred_raiseload` 

1694 

1695 """ 

1696 

1697 return loadopt.set_relationship_strategy( 

1698 attr, {"lazy": "raise_on_sql" if sql_only else "raise"} 

1699 ) 

1700 

1701 

1702@raiseload._add_unbound_fn 

1703def raiseload(*keys, **kw): 

1704 return _UnboundLoad._from_keys(_UnboundLoad.raiseload, keys, False, kw) 

1705 

1706 

1707@loader_option() 

1708def defaultload(loadopt, attr): 

1709 """Indicate an attribute should load using its default loader style. 

1710 

1711 This method is used to link to other loader options further into 

1712 a chain of attributes without altering the loader style of the links 

1713 along the chain. For example, to set joined eager loading for an 

1714 element of an element:: 

1715 

1716 session.query(MyClass).options( 

1717 defaultload(MyClass.someattribute). 

1718 joinedload(MyOtherClass.someotherattribute) 

1719 ) 

1720 

1721 :func:`.defaultload` is also useful for setting column-level options 

1722 on a related class, namely that of :func:`.defer` and :func:`.undefer`:: 

1723 

1724 session.query(MyClass).options( 

1725 defaultload(MyClass.someattribute). 

1726 defer("some_column"). 

1727 undefer("some_other_column") 

1728 ) 

1729 

1730 .. seealso:: 

1731 

1732 :meth:`_orm.Load.options` - allows for complex hierarchical 

1733 loader option structures with less verbosity than with individual 

1734 :func:`.defaultload` directives. 

1735 

1736 :ref:`relationship_loader_options` 

1737 

1738 :ref:`deferred_loading_w_multiple` 

1739 

1740 """ 

1741 return loadopt.set_relationship_strategy(attr, None) 

1742 

1743 

1744@defaultload._add_unbound_fn 

1745def defaultload(*keys): 

1746 return _UnboundLoad._from_keys(_UnboundLoad.defaultload, keys, False, {}) 

1747 

1748 

1749@loader_option() 

1750def defer(loadopt, key, raiseload=False): 

1751 r"""Indicate that the given column-oriented attribute should be deferred, 

1752 e.g. not loaded until accessed. 

1753 

1754 This function is part of the :class:`_orm.Load` interface and supports 

1755 both method-chained and standalone operation. 

1756 

1757 e.g.:: 

1758 

1759 from sqlalchemy.orm import defer 

1760 

1761 session.query(MyClass).options( 

1762 defer("attribute_one"), 

1763 defer("attribute_two")) 

1764 

1765 session.query(MyClass).options( 

1766 defer(MyClass.attribute_one), 

1767 defer(MyClass.attribute_two)) 

1768 

1769 To specify a deferred load of an attribute on a related class, 

1770 the path can be specified one token at a time, specifying the loading 

1771 style for each link along the chain. To leave the loading style 

1772 for a link unchanged, use :func:`_orm.defaultload`:: 

1773 

1774 session.query(MyClass).options(defaultload("someattr").defer("some_column")) 

1775 

1776 A :class:`_orm.Load` object that is present on a certain path can have 

1777 :meth:`_orm.Load.defer` called multiple times, 

1778 each will operate on the same 

1779 parent entity:: 

1780 

1781 

1782 session.query(MyClass).options( 

1783 defaultload("someattr"). 

1784 defer("some_column"). 

1785 defer("some_other_column"). 

1786 defer("another_column") 

1787 ) 

1788 

1789 :param key: Attribute to be deferred. 

1790 

1791 :param raiseload: raise :class:`.InvalidRequestError` if the column 

1792 value is to be loaded from emitting SQL. Used to prevent unwanted 

1793 SQL from being emitted. 

1794 

1795 .. versionadded:: 1.4 

1796 

1797 .. seealso:: 

1798 

1799 :ref:`deferred_raiseload` 

1800 

1801 :param \*addl_attrs: This option supports the old 0.8 style 

1802 of specifying a path as a series of attributes, which is now superseded 

1803 by the method-chained style. 

1804 

1805 .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.defer` is 

1806 deprecated and will be removed in a future release. Please 

1807 use method chaining in conjunction with defaultload() to 

1808 indicate a path. 

1809 

1810 

1811 .. seealso:: 

1812 

1813 :ref:`deferred` 

1814 

1815 :func:`_orm.undefer` 

1816 

1817 """ 

1818 strategy = {"deferred": True, "instrument": True} 

1819 if raiseload: 

1820 strategy["raiseload"] = True 

1821 return loadopt.set_column_strategy((key,), strategy) 

1822 

1823 

1824@defer._add_unbound_fn 

1825def defer(key, *addl_attrs, **kw): 

1826 if addl_attrs: 

1827 util.warn_deprecated( 

1828 "The *addl_attrs on orm.defer is deprecated. Please use " 

1829 "method chaining in conjunction with defaultload() to " 

1830 "indicate a path.", 

1831 version="1.3", 

1832 ) 

1833 return _UnboundLoad._from_keys( 

1834 _UnboundLoad.defer, (key,) + addl_attrs, False, kw 

1835 ) 

1836 

1837 

1838@loader_option() 

1839def undefer(loadopt, key): 

1840 r"""Indicate that the given column-oriented attribute should be undeferred, 

1841 e.g. specified within the SELECT statement of the entity as a whole. 

1842 

1843 The column being undeferred is typically set up on the mapping as a 

1844 :func:`.deferred` attribute. 

1845 

1846 This function is part of the :class:`_orm.Load` interface and supports 

1847 both method-chained and standalone operation. 

1848 

1849 Examples:: 

1850 

1851 # undefer two columns 

1852 session.query(MyClass).options(undefer("col1"), undefer("col2")) 

1853 

1854 # undefer all columns specific to a single class using Load + * 

1855 session.query(MyClass, MyOtherClass).options( 

1856 Load(MyClass).undefer("*")) 

1857 

1858 # undefer a column on a related object 

1859 session.query(MyClass).options( 

1860 defaultload(MyClass.items).undefer('text')) 

1861 

1862 :param key: Attribute to be undeferred. 

1863 

1864 :param \*addl_attrs: This option supports the old 0.8 style 

1865 of specifying a path as a series of attributes, which is now superseded 

1866 by the method-chained style. 

1867 

1868 .. deprecated:: 0.9 The \*addl_attrs on :func:`_orm.undefer` is 

1869 deprecated and will be removed in a future release. Please 

1870 use method chaining in conjunction with defaultload() to 

1871 indicate a path. 

1872 

1873 .. seealso:: 

1874 

1875 :ref:`deferred` 

1876 

1877 :func:`_orm.defer` 

1878 

1879 :func:`_orm.undefer_group` 

1880 

1881 """ 

1882 return loadopt.set_column_strategy( 

1883 (key,), {"deferred": False, "instrument": True} 

1884 ) 

1885 

1886 

1887@undefer._add_unbound_fn 

1888def undefer(key, *addl_attrs): 

1889 if addl_attrs: 

1890 util.warn_deprecated( 

1891 "The *addl_attrs on orm.undefer is deprecated. Please use " 

1892 "method chaining in conjunction with defaultload() to " 

1893 "indicate a path.", 

1894 version="1.3", 

1895 ) 

1896 return _UnboundLoad._from_keys( 

1897 _UnboundLoad.undefer, (key,) + addl_attrs, False, {} 

1898 ) 

1899 

1900 

1901@loader_option() 

1902def undefer_group(loadopt, name): 

1903 """Indicate that columns within the given deferred group name should be 

1904 undeferred. 

1905 

1906 The columns being undeferred are set up on the mapping as 

1907 :func:`.deferred` attributes and include a "group" name. 

1908 

1909 E.g:: 

1910 

1911 session.query(MyClass).options(undefer_group("large_attrs")) 

1912 

1913 To undefer a group of attributes on a related entity, the path can be 

1914 spelled out using relationship loader options, such as 

1915 :func:`_orm.defaultload`:: 

1916 

1917 session.query(MyClass).options( 

1918 defaultload("someattr").undefer_group("large_attrs")) 

1919 

1920 .. versionchanged:: 0.9.0 :func:`_orm.undefer_group` is now specific to a 

1921 particular entity load path. 

1922 

1923 .. seealso:: 

1924 

1925 :ref:`deferred` 

1926 

1927 :func:`_orm.defer` 

1928 

1929 :func:`_orm.undefer` 

1930 

1931 """ 

1932 return loadopt.set_column_strategy( 

1933 "*", None, {"undefer_group_%s" % name: True}, opts_only=True 

1934 ) 

1935 

1936 

1937@undefer_group._add_unbound_fn 

1938def undefer_group(name): 

1939 return _UnboundLoad().undefer_group(name) 

1940 

1941 

1942@loader_option() 

1943def with_expression(loadopt, key, expression): 

1944 r"""Apply an ad-hoc SQL expression to a "deferred expression" attribute. 

1945 

1946 This option is used in conjunction with the :func:`_orm.query_expression` 

1947 mapper-level construct that indicates an attribute which should be the 

1948 target of an ad-hoc SQL expression. 

1949 

1950 E.g.:: 

1951 

1952 

1953 sess.query(SomeClass).options( 

1954 with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y) 

1955 ) 

1956 

1957 .. versionadded:: 1.2 

1958 

1959 :param key: Attribute to be populated. 

1960 

1961 :param expr: SQL expression to be applied to the attribute. 

1962 

1963 .. versionchanged:: 1.4 Loader options such as 

1964 :func:`_orm.with_expression` 

1965 take effect only at the **outermost** query used, and should not be used 

1966 within subqueries or inner elements of a UNION. See the change notes at 

1967 :ref:`change_8879` for background on how to correctly add arbitrary 

1968 columns to subqueries. 

1969 

1970 .. note:: the target attribute is populated only if the target object 

1971 is **not currently loaded** in the current :class:`_orm.Session` 

1972 unless the :meth:`_query.Query.populate_existing` method is used. 

1973 Please refer to :ref:`mapper_querytime_expression` for complete 

1974 usage details. 

1975 

1976 .. seealso:: 

1977 

1978 :ref:`mapper_querytime_expression` 

1979 

1980 """ 

1981 

1982 expression = coercions.expect( 

1983 roles.LabeledColumnExprRole, _orm_full_deannotate(expression) 

1984 ) 

1985 

1986 return loadopt.set_column_strategy( 

1987 (key,), {"query_expression": True}, opts={"expression": expression} 

1988 ) 

1989 

1990 

1991@with_expression._add_unbound_fn 

1992def with_expression(key, expression): 

1993 return _UnboundLoad._from_keys( 

1994 _UnboundLoad.with_expression, (key,), False, {"expression": expression} 

1995 ) 

1996 

1997 

1998@loader_option() 

1999def selectin_polymorphic(loadopt, classes): 

2000 """Indicate an eager load should take place for all attributes 

2001 specific to a subclass. 

2002 

2003 This uses an additional SELECT with IN against all matched primary 

2004 key values, and is the per-query analogue to the ``"selectin"`` 

2005 setting on the :paramref:`.mapper.polymorphic_load` parameter. 

2006 

2007 .. versionadded:: 1.2 

2008 

2009 .. seealso:: 

2010 

2011 :ref:`polymorphic_selectin` 

2012 

2013 """ 

2014 loadopt.set_class_strategy( 

2015 {"selectinload_polymorphic": True}, 

2016 opts={ 

2017 "entities": tuple( 

2018 sorted((inspect(cls) for cls in classes), key=id) 

2019 ) 

2020 }, 

2021 ) 

2022 return loadopt 

2023 

2024 

2025@selectin_polymorphic._add_unbound_fn 

2026def selectin_polymorphic(base_cls, classes): 

2027 ul = _UnboundLoad() 

2028 ul.is_class_strategy = True 

2029 ul.path = (inspect(base_cls),) 

2030 ul.selectin_polymorphic(classes) 

2031 return ul