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

650 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# Copyright (C) 2005-2022 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: https://www.opensource.org/licenses/mit-license.php 

6 

7""" 

8 

9""" 

10 

11from . import util as orm_util 

12from .attributes import QueryableAttribute 

13from .base import _class_to_mapper 

14from .base import _is_aliased_class 

15from .base import _is_mapped_class 

16from .base import InspectionAttr 

17from .interfaces import LoaderOption 

18from .interfaces import MapperProperty 

19from .interfaces import PropComparator 

20from .path_registry import _DEFAULT_TOKEN 

21from .path_registry import _WILDCARD_TOKEN 

22from .path_registry import PathRegistry 

23from .path_registry import TokenRegistry 

24from .util import _orm_full_deannotate 

25from .. import exc as sa_exc 

26from .. import inspect 

27from .. import util 

28from ..sql import and_ 

29from ..sql import coercions 

30from ..sql import roles 

31from ..sql import traversals 

32from ..sql import visitors 

33from ..sql.base import _generative 

34from ..sql.base import Generative 

35 

36 

37class Load(Generative, LoaderOption): 

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

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

40 loaded. 

41 

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

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

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

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

46 

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

48 class as the argument. This style of usage is 

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

50 that has multiple entities:: 

51 

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

53 

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

55 where it 

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

57 

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

59 

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

61 is useful as public API is when specifying 

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

63 

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

65 

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

67 attributes on those descendant objects will load using their normal 

68 loader strategy. 

69 

70 .. seealso:: 

71 

72 :ref:`deferred_options` 

73 

74 :ref:`deferred_loading_w_multiple` 

75 

76 :ref:`relationship_loader_options` 

77 

78 """ 

79 

80 _is_strategy_option = True 

81 

82 _cache_key_traversal = [ 

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

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

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

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

87 ( 

88 "_context_cache_key", 

89 visitors.ExtendedInternalTraversal.dp_has_cache_key_tuples, 

90 ), 

91 ( 

92 "local_opts", 

93 visitors.ExtendedInternalTraversal.dp_string_multi_dict, 

94 ), 

95 ] 

96 

97 def __init__(self, entity): 

98 insp = inspect(entity) 

99 insp._post_inspect 

100 

101 self.path = insp._path_registry 

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

103 # Load objects 

104 self.context = util.OrderedDict() 

105 self.local_opts = {} 

106 self.is_class_strategy = False 

107 

108 @classmethod 

109 def for_existing_path(cls, path): 

110 load = cls.__new__(cls) 

111 load.path = path 

112 load.context = {} 

113 load.local_opts = {} 

114 load._of_type = None 

115 load._extra_criteria = () 

116 return load 

117 

118 def _adapt_cached_option_to_uncached_option(self, context, uncached_opt): 

119 return self._adjust_for_extra_criteria(context) 

120 

121 def _generate_extra_criteria(self, context): 

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

123 immediate "extra_criteria" stored with this Load object. 

124 

125 Load objects are typically pulled from the cached version of 

126 the statement from a QueryContext. The statement currently being 

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

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

129 they handle this criteria for a result set. 

130 

131 """ 

132 

133 assert ( 

134 self._extra_criteria 

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

136 

137 orig_query = context.compile_state.select_statement 

138 current_query = context.query 

139 

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

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

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

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

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

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

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

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

148 

149 # if orig_query is current_query: 

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

151 # return and_(*self._extra_criteria) 

152 

153 k1 = orig_query._generate_cache_key() 

154 k2 = current_query._generate_cache_key() 

155 

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

157 

158 def _adjust_for_extra_criteria(self, context): 

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

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

161 copying in place. 

162 

163 """ 

164 orig_query = context.compile_state.select_statement 

165 

166 applied = {} 

167 

168 ck = [None, None] 

169 

170 def process(opt): 

171 if not opt._extra_criteria: 

172 return 

173 

174 if ck[0] is None: 

175 ck[:] = ( 

176 orig_query._generate_cache_key(), 

177 context.query._generate_cache_key(), 

178 ) 

179 k1, k2 = ck 

180 

181 opt._extra_criteria = tuple( 

182 k2._apply_params_to_element(k1, crit) 

183 for crit in opt._extra_criteria 

184 ) 

185 

186 return self._deep_clone(applied, process) 

187 

188 def _deep_clone(self, applied, process): 

189 if self in applied: 

190 return applied[self] 

191 

192 cloned = self._generate() 

193 

194 applied[self] = cloned 

195 

196 cloned.strategy = self.strategy 

197 

198 assert cloned.propagate_to_loaders == self.propagate_to_loaders 

199 assert cloned.is_class_strategy == self.is_class_strategy 

200 assert cloned.is_opts_only == self.is_opts_only 

201 

202 if self.context: 

203 cloned.context = util.OrderedDict( 

204 [ 

205 ( 

206 key, 

207 value._deep_clone(applied, process) 

208 if isinstance(value, Load) 

209 else value, 

210 ) 

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

212 ] 

213 ) 

214 

215 cloned.local_opts.update(self.local_opts) 

216 

217 process(cloned) 

218 

219 return cloned 

220 

221 @property 

222 def _context_cache_key(self): 

223 serialized = [] 

224 if self.context is None: 

225 return [] 

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

227 if key != "loader": 

228 continue 

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

230 return serialized 

231 

232 def _generate(self): 

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

234 cloned.local_opts = {} 

235 return cloned 

236 

237 is_opts_only = False 

238 is_class_strategy = False 

239 strategy = None 

240 propagate_to_loaders = False 

241 _of_type = None 

242 _extra_criteria = () 

243 

244 def process_compile_state_replaced_entities( 

245 self, compile_state, mapper_entities 

246 ): 

247 if not compile_state.compile_options._enable_eagerloads: 

248 return 

249 

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

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

252 # for the entities having been replaced with equivalents 

253 self._process( 

254 compile_state, 

255 mapper_entities, 

256 not bool(compile_state.current_path), 

257 ) 

258 

259 def process_compile_state(self, compile_state): 

260 if not compile_state.compile_options._enable_eagerloads: 

261 return 

262 

263 self._process( 

264 compile_state, 

265 compile_state._lead_mapper_entities, 

266 not bool(compile_state.current_path) 

267 and not compile_state.compile_options._for_refresh_state, 

268 ) 

269 

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

271 is_refresh = compile_state.compile_options._for_refresh_state 

272 current_path = compile_state.current_path 

273 if current_path: 

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

275 if is_refresh and not loader.propagate_to_loaders: 

276 continue 

277 chopped_start_path = self._chop_path(start_path, current_path) 

278 if chopped_start_path is not None: 

279 compile_state.attributes[ 

280 (token, chopped_start_path) 

281 ] = loader 

282 else: 

283 compile_state.attributes.update(self.context) 

284 

285 def _generate_path( 

286 self, 

287 path, 

288 attr, 

289 for_strategy, 

290 wildcard_key, 

291 raiseerr=True, 

292 polymorphic_entity_context=None, 

293 ): 

294 existing_of_type = self._of_type 

295 self._of_type = None 

296 if raiseerr and not path.has_entity: 

297 if isinstance(path, TokenRegistry): 

298 raise sa_exc.ArgumentError( 

299 "Wildcard token cannot be followed by another entity" 

300 ) 

301 else: 

302 raise sa_exc.ArgumentError( 

303 "Mapped attribute '%s' does not " 

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

305 ) 

306 

307 if isinstance(attr, util.string_types): 

308 

309 default_token = attr.endswith(_DEFAULT_TOKEN) 

310 attr_str_name = attr 

311 if attr.endswith(_WILDCARD_TOKEN) or default_token: 

312 if default_token: 

313 self.propagate_to_loaders = False 

314 if wildcard_key: 

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

316 

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

318 # working for a with_polymorphic entity because the 

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

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

321 if existing_of_type and not existing_of_type.is_aliased_class: 

322 path = path.parent[existing_of_type] 

323 path = path.token(attr) 

324 self.path = path 

325 return path 

326 

327 if existing_of_type: 

328 ent = inspect(existing_of_type) 

329 else: 

330 ent = path.entity 

331 

332 util.warn_deprecated_20( 

333 "Using strings to indicate column or " 

334 "relationship paths in loader options is deprecated " 

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

336 "the class-bound attribute directly.", 

337 ) 

338 try: 

339 # use getattr on the class to work around 

340 # synonyms, hybrids, etc. 

341 attr = getattr(ent.class_, attr) 

342 except AttributeError as err: 

343 if raiseerr: 

344 util.raise_( 

345 sa_exc.ArgumentError( 

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

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

348 ), 

349 replace_context=err, 

350 ) 

351 else: 

352 return None 

353 else: 

354 try: 

355 attr = found_property = attr.property 

356 except AttributeError as ae: 

357 if not isinstance(attr, MapperProperty): 

358 util.raise_( 

359 sa_exc.ArgumentError( 

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

361 "mapped attribute; " 

362 "instead got %s object." 

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

364 ), 

365 replace_context=ae, 

366 ) 

367 else: 

368 raise 

369 

370 path = path[attr] 

371 else: 

372 insp = inspect(attr) 

373 

374 if insp.is_mapper or insp.is_aliased_class: 

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

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

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

378 if not orm_util._entity_corresponds_to_use_path_impl( 

379 attr.parent, path[-1] 

380 ): 

381 if raiseerr: 

382 raise sa_exc.ArgumentError( 

383 "Attribute '%s' does not " 

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

385 ) 

386 else: 

387 return None 

388 elif insp.is_property: 

389 prop = found_property = attr 

390 path = path[prop] 

391 elif insp.is_attribute: 

392 prop = found_property = attr.property 

393 

394 if not orm_util._entity_corresponds_to_use_path_impl( 

395 attr.parent, path[-1] 

396 ): 

397 if raiseerr: 

398 raise sa_exc.ArgumentError( 

399 'Attribute "%s" does not ' 

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

401 % ( 

402 attr, 

403 path.entity, 

404 ( 

405 " Did you mean to use " 

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

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

408 if len(path) > 1 

409 and path.entity.is_mapper 

410 and attr.parent.is_aliased_class 

411 else "" 

412 ), 

413 ) 

414 ) 

415 else: 

416 return None 

417 

418 if attr._extra_criteria and not self._extra_criteria: 

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

420 # already established _extra_criteria. however if not, 

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

422 self._extra_criteria = attr._extra_criteria 

423 

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

425 ac = attr._of_type 

426 ext_info = of_type_info = inspect(ac) 

427 

428 if polymorphic_entity_context is None: 

429 polymorphic_entity_context = self.context 

430 

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

432 polymorphic_entity_context, "path_with_polymorphic" 

433 ) 

434 

435 if not ext_info.is_aliased_class: 

436 ac = orm_util.with_polymorphic( 

437 ext_info.mapper.base_mapper, 

438 ext_info.mapper, 

439 aliased=True, 

440 _use_mapper_path=True, 

441 _existing_alias=inspect(existing) 

442 if existing is not None 

443 else None, 

444 ) 

445 

446 ext_info = inspect(ac) 

447 

448 path.entity_path[prop].set( 

449 polymorphic_entity_context, "path_with_polymorphic", ac 

450 ) 

451 

452 path = path[prop][ext_info] 

453 

454 self._of_type = of_type_info 

455 

456 else: 

457 path = path[prop] 

458 

459 if for_strategy is not None: 

460 found_property._get_strategy(for_strategy) 

461 if path.has_entity: 

462 path = path.entity_path 

463 self.path = path 

464 return path 

465 

466 def __str__(self): 

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

468 

469 def _coerce_strat(self, strategy): 

470 if strategy is not None: 

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

472 return strategy 

473 

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

475 raise NotImplementedError( 

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

477 "Load.options() method" 

478 ) 

479 

480 @_generative 

481 def options(self, *opts): 

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

483 :class:`_orm.Load` 

484 object. 

485 

486 E.g.:: 

487 

488 query = session.query(Author) 

489 query = query.options( 

490 joinedload(Author.book).options( 

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

492 joinedload(Book.citations).options( 

493 joinedload(Citation.author) 

494 ) 

495 ) 

496 ) 

497 

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

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

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

501 

502 .. versionadded:: 1.3.6 

503 

504 .. seealso:: 

505 

506 :func:`.defaultload` 

507 

508 :ref:`relationship_loader_options` 

509 

510 :ref:`deferred_loading_w_multiple` 

511 

512 """ 

513 apply_cache = {} 

514 bound = not isinstance(self, _UnboundLoad) 

515 if bound: 

516 raise NotImplementedError( 

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

518 "for 'unbound' loader options" 

519 ) 

520 for opt in opts: 

521 try: 

522 opt._apply_to_parent(self, apply_cache, bound) 

523 except AttributeError as ae: 

524 if not isinstance(opt, Load): 

525 util.raise_( 

526 sa_exc.ArgumentError( 

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

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

529 ), 

530 from_=ae, 

531 ) 

532 else: 

533 raise 

534 

535 @_generative 

536 def set_relationship_strategy( 

537 self, attr, strategy, propagate_to_loaders=True 

538 ): 

539 strategy = self._coerce_strat(strategy) 

540 self.propagate_to_loaders = propagate_to_loaders 

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

542 self.path = cloned.path 

543 self._of_type = cloned._of_type 

544 self._extra_criteria = cloned._extra_criteria 

545 cloned.is_class_strategy = self.is_class_strategy = False 

546 self.propagate_to_loaders = cloned.propagate_to_loaders 

547 

548 @_generative 

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

550 strategy = self._coerce_strat(strategy) 

551 self.is_class_strategy = False 

552 for attr in attrs: 

553 cloned = self._clone_for_bind_strategy( 

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

555 ) 

556 cloned.propagate_to_loaders = True 

557 

558 @_generative 

559 def set_generic_strategy(self, attrs, strategy): 

560 strategy = self._coerce_strat(strategy) 

561 for attr in attrs: 

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

563 cloned.propagate_to_loaders = True 

564 

565 @_generative 

566 def set_class_strategy(self, strategy, opts): 

567 strategy = self._coerce_strat(strategy) 

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

569 cloned.is_class_strategy = True 

570 cloned.propagate_to_loaders = True 

571 cloned.local_opts.update(opts) 

572 

573 def _clone_for_bind_strategy( 

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

575 ): 

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

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

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

579 in order to not create reference cycles. 

580 

581 """ 

582 cloned = self._generate() 

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

584 cloned.strategy = strategy 

585 

586 cloned.local_opts = self.local_opts 

587 if opts: 

588 cloned.local_opts.update(opts) 

589 if opts_only: 

590 cloned.is_opts_only = True 

591 

592 if strategy or cloned.is_opts_only: 

593 cloned._set_path_strategy() 

594 return cloned 

595 

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

597 if merge_opts or not replace: 

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

599 if existing: 

600 if merge_opts: 

601 existing.local_opts.update(self.local_opts) 

602 existing._extra_criteria += self._extra_criteria 

603 else: 

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

605 else: 

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

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

608 if existing and existing.is_opts_only: 

609 self.local_opts.update(existing.local_opts) 

610 existing._extra_criteria += self._extra_criteria 

611 

612 def _set_path_strategy(self): 

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

614 effective_path = self.path.parent 

615 else: 

616 effective_path = self.path 

617 

618 if effective_path.is_token: 

619 for path in effective_path.generate_for_superclasses(): 

620 self._set_for_path( 

621 self.context, 

622 path, 

623 replace=True, 

624 merge_opts=self.is_opts_only, 

625 ) 

626 else: 

627 self._set_for_path( 

628 self.context, 

629 effective_path, 

630 replace=True, 

631 merge_opts=self.is_opts_only, 

632 ) 

633 

634 # remove cycles; _set_path_strategy is always invoked on an 

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

636 self.context = None 

637 

638 def __getstate__(self): 

639 d = self.__dict__.copy() 

640 

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

642 d["_extra_criteria"] = () 

643 

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

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

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

647 ) 

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

649 return d 

650 

651 def __setstate__(self, state): 

652 self.__dict__.update(state) 

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

654 if self.context is not None: 

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

656 

657 def _chop_path(self, to_chop, path): 

658 i = -1 

659 

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

661 if isinstance(c_token, util.string_types): 

662 # TODO: this is approximated from the _UnboundLoad 

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

664 

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

666 return to_chop 

667 elif ( 

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

669 and c_token != p_token.key 

670 ): 

671 return None 

672 

673 if c_token is p_token: 

674 continue 

675 elif ( 

676 isinstance(c_token, InspectionAttr) 

677 and c_token.is_mapper 

678 and p_token.is_mapper 

679 and c_token.isa(p_token) 

680 ): 

681 continue 

682 else: 

683 return None 

684 return to_chop[i + 1 :] 

685 

686 

687class _UnboundLoad(Load): 

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

689 

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

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

692 

693 This provides compatibility with the traditional system 

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

695 

696 """ 

697 

698 def __init__(self): 

699 self.path = () 

700 self._to_bind = [] 

701 self.local_opts = {} 

702 self._extra_criteria = () 

703 

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

705 """Inlined gen_cache_key 

706 

707 Original traversal is:: 

708 

709 

710 _cache_key_traversal = [ 

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

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

713 ( 

714 "_to_bind", 

715 visitors.ExtendedInternalTraversal.dp_has_cache_key_list, 

716 ), 

717 ( 

718 "_extra_criteria", 

719 visitors.InternalTraversal.dp_clauseelement_list), 

720 ( 

721 "local_opts", 

722 visitors.ExtendedInternalTraversal.dp_string_multi_dict, 

723 ), 

724 ] 

725 

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

727 repeat the same UnboundLoad options over and over again. 

728 

729 See #6869 

730 

731 """ 

732 

733 idself = id(self) 

734 cls = self.__class__ 

735 

736 if idself in anon_map: 

737 return (anon_map[idself], cls) 

738 else: 

739 id_ = anon_map[idself] 

740 

741 vis = traversals._cache_key_traversal_visitor 

742 

743 seen = _unbound_option_seen 

744 if seen is None: 

745 seen = set() 

746 

747 return ( 

748 (id_, cls) 

749 + vis.visit_multi_list( 

750 "path", self.path, self, anon_map, bindparams 

751 ) 

752 + ("strategy", self.strategy) 

753 + ( 

754 ( 

755 "_to_bind", 

756 tuple( 

757 elem._gen_cache_key( 

758 anon_map, bindparams, _unbound_option_seen=seen 

759 ) 

760 for elem in self._to_bind 

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

762 ), 

763 ) 

764 if self._to_bind 

765 else () 

766 ) 

767 + ( 

768 ( 

769 "_extra_criteria", 

770 tuple( 

771 elem._gen_cache_key(anon_map, bindparams) 

772 for elem in self._extra_criteria 

773 ), 

774 ) 

775 if self._extra_criteria 

776 else () 

777 ) 

778 + ( 

779 vis.visit_string_multi_dict( 

780 "local_opts", self.local_opts, self, anon_map, bindparams 

781 ) 

782 if self.local_opts 

783 else () 

784 ) 

785 ) 

786 

787 _is_chain_link = False 

788 

789 def _set_path_strategy(self): 

790 self._to_bind.append(self) 

791 

792 # remove cycles; _set_path_strategy is always invoked on an 

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

794 self._to_bind = None 

795 

796 def _deep_clone(self, applied, process): 

797 if self in applied: 

798 return applied[self] 

799 

800 cloned = self._generate() 

801 

802 applied[self] = cloned 

803 

804 cloned.strategy = self.strategy 

805 

806 assert cloned.propagate_to_loaders == self.propagate_to_loaders 

807 assert cloned.is_class_strategy == self.is_class_strategy 

808 assert cloned.is_opts_only == self.is_opts_only 

809 

810 cloned._to_bind = [ 

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

812 ] 

813 

814 cloned.local_opts.update(self.local_opts) 

815 

816 process(cloned) 

817 

818 return cloned 

819 

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

821 if self in applied: 

822 return applied[self] 

823 

824 if to_bind is None: 

825 to_bind = self._to_bind 

826 

827 cloned = self._generate() 

828 

829 applied[self] = cloned 

830 

831 cloned.strategy = self.strategy 

832 if self.path: 

833 attr = self.path[-1] 

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

835 _DEFAULT_TOKEN 

836 ): 

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

838 cloned._generate_path( 

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

840 ) 

841 

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

843 # mature 

844 assert cloned.propagate_to_loaders == self.propagate_to_loaders 

845 assert cloned.is_class_strategy == self.is_class_strategy 

846 assert cloned.is_opts_only == self.is_opts_only 

847 

848 uniq = set() 

849 

850 cloned._to_bind = parent._to_bind 

851 

852 cloned._to_bind[:] = [ 

853 elem 

854 for elem in cloned._to_bind 

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

856 ] + [ 

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

858 for elem in to_bind 

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

860 ] 

861 

862 cloned.local_opts.update(self.local_opts) 

863 

864 return cloned 

865 

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

867 if ( 

868 wildcard_key 

869 and isinstance(attr, util.string_types) 

870 and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN) 

871 ): 

872 if attr == _DEFAULT_TOKEN: 

873 self.propagate_to_loaders = False 

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

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

876 path = path[0:-1] 

877 if attr: 

878 path = path + (attr,) 

879 self.path = path 

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

881 

882 return path 

883 

884 def __getstate__(self): 

885 d = self.__dict__.copy() 

886 

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

888 d["_extra_criteria"] = () 

889 

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

891 return d 

892 

893 def __setstate__(self, state): 

894 ret = [] 

895 for key in state["path"]: 

896 if isinstance(key, tuple): 

897 if len(key) == 2: 

898 # support legacy 

899 cls, propkey = key 

900 of_type = None 

901 else: 

902 cls, propkey, of_type = key 

903 prop = getattr(cls, propkey) 

904 if of_type: 

905 prop = prop.of_type(of_type) 

906 ret.append(prop) 

907 else: 

908 ret.append(key) 

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

910 self.__dict__ = state 

911 

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

913 dedupes = compile_state.attributes["_unbound_load_dedupes"] 

914 is_refresh = compile_state.compile_options._for_refresh_state 

915 for val in self._to_bind: 

916 if val not in dedupes: 

917 dedupes.add(val) 

918 if is_refresh and not val.propagate_to_loaders: 

919 continue 

920 val._bind_loader( 

921 [ent.entity_zero for ent in mapper_entities], 

922 compile_state.current_path, 

923 compile_state.attributes, 

924 raiseerr, 

925 ) 

926 

927 @classmethod 

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

929 opt = _UnboundLoad() 

930 

931 def _split_key(key): 

932 if isinstance(key, util.string_types): 

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

934 if key == _WILDCARD_TOKEN: 

935 return (_DEFAULT_TOKEN,) 

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

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

938 util.warn_deprecated( 

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

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

941 "believed to be unused. " 

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

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

944 "tracker.", 

945 version="1.4", 

946 ) 

947 key = key[1:] 

948 return key.split(".") 

949 else: 

950 return (key,) 

951 

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

953 

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

955 # set _is_chain_link first so that clones of the 

956 # object also inherit this flag 

957 opt._is_chain_link = True 

958 if chained: 

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

960 else: 

961 opt = opt.defaultload(token) 

962 

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

964 opt._is_chain_link = False 

965 return opt 

966 

967 def _chop_path(self, to_chop, path): 

968 i = -1 

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

970 zip(to_chop, path.pairs()) 

971 ): 

972 if isinstance(c_token, util.string_types): 

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

974 return to_chop 

975 elif ( 

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

977 and c_token != p_prop.key 

978 ): 

979 return None 

980 elif isinstance(c_token, PropComparator): 

981 if c_token.property is not p_prop or ( 

982 c_token._parententity is not p_entity 

983 and ( 

984 not c_token._parententity.is_mapper 

985 or not c_token._parententity.isa(p_entity) 

986 ) 

987 ): 

988 return None 

989 else: 

990 i += 1 

991 

992 return to_chop[i:] 

993 

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

995 ret = [] 

996 for token in path: 

997 if isinstance(token, QueryableAttribute): 

998 if ( 

999 filter_aliased_class 

1000 and token._of_type 

1001 and inspect(token._of_type).is_aliased_class 

1002 ): 

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

1004 else: 

1005 ret.append( 

1006 ( 

1007 token._parentmapper.class_, 

1008 token.key, 

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

1010 ) 

1011 ) 

1012 elif isinstance(token, PropComparator): 

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

1014 else: 

1015 ret.append(token) 

1016 return ret 

1017 

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

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

1020 

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

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

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

1024 given Query into a Load. 

1025 

1026 Example:: 

1027 

1028 

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

1030 joinedload("orders").joinedload("items")) 

1031 

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

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

1034 

1035 _UnboundLoad( 

1036 _to_bind=[ 

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

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

1039 ] 

1040 ) 

1041 

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

1043 not exact API):: 

1044 

1045 Load( 

1046 User, 

1047 (User, User.orders.property)) 

1048 Load( 

1049 User, 

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

1051 

1052 """ 

1053 

1054 start_path = self.path 

1055 

1056 if self.is_class_strategy and current_path: 

1057 start_path += (entities[0],) 

1058 

1059 # _current_path implies we're in a 

1060 # secondary load with an existing path 

1061 

1062 if current_path: 

1063 start_path = self._chop_path(start_path, current_path) 

1064 

1065 if not start_path: 

1066 return None 

1067 

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

1069 # what entity we are referring towards. 

1070 token = start_path[0] 

1071 

1072 if isinstance(token, util.string_types): 

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

1074 elif isinstance(token, PropComparator): 

1075 prop = token.property 

1076 entity = self._find_entity_prop_comparator( 

1077 entities, prop, token._parententity, raiseerr 

1078 ) 

1079 elif self.is_class_strategy and _is_mapped_class(token): 

1080 entity = inspect(token) 

1081 if entity not in entities: 

1082 entity = None 

1083 else: 

1084 raise sa_exc.ArgumentError( 

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

1086 ) 

1087 

1088 if not entity: 

1089 return 

1090 

1091 path_element = entity 

1092 

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

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

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

1096 # tokens and populate into the Load(). 

1097 loader = Load(path_element) 

1098 

1099 if context is None: 

1100 context = loader.context 

1101 

1102 loader.strategy = self.strategy 

1103 loader.is_opts_only = self.is_opts_only 

1104 loader.is_class_strategy = self.is_class_strategy 

1105 loader._extra_criteria = self._extra_criteria 

1106 

1107 path = loader.path 

1108 

1109 if not loader.is_class_strategy: 

1110 for idx, token in enumerate(start_path): 

1111 if not loader._generate_path( 

1112 loader.path, 

1113 token, 

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

1115 None, 

1116 raiseerr, 

1117 polymorphic_entity_context=context, 

1118 ): 

1119 return 

1120 

1121 loader.local_opts.update(self.local_opts) 

1122 

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

1124 effective_path = loader.path.parent 

1125 else: 

1126 effective_path = loader.path 

1127 

1128 # prioritize "first class" options over those 

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

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

1131 

1132 if effective_path.is_token: 

1133 for path in effective_path.generate_for_superclasses(): 

1134 loader._set_for_path( 

1135 context, 

1136 path, 

1137 replace=not self._is_chain_link, 

1138 merge_opts=self.is_opts_only, 

1139 ) 

1140 else: 

1141 loader._set_for_path( 

1142 context, 

1143 effective_path, 

1144 replace=not self._is_chain_link, 

1145 merge_opts=self.is_opts_only, 

1146 ) 

1147 

1148 return loader 

1149 

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

1151 if _is_aliased_class(mapper): 

1152 searchfor = mapper 

1153 else: 

1154 searchfor = _class_to_mapper(mapper) 

1155 for ent in entities: 

1156 if orm_util._entity_corresponds_to(ent, searchfor): 

1157 return ent 

1158 else: 

1159 if raiseerr: 

1160 if not list(entities): 

1161 raise sa_exc.ArgumentError( 

1162 "Query has only expression-based entities, " 

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

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

1165 ) 

1166 else: 

1167 raise sa_exc.ArgumentError( 

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

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

1170 "specify the full path " 

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

1172 "attribute. " 

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

1174 ) 

1175 else: 

1176 return None 

1177 

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

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

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

1181 if raiseerr: 

1182 raise sa_exc.ArgumentError( 

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

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

1185 "loader options for each entity individually, such " 

1186 "as %s." 

1187 % ( 

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

1189 ", ".join( 

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

1191 for ent in entities 

1192 ), 

1193 ) 

1194 ) 

1195 elif token.endswith(_DEFAULT_TOKEN): 

1196 raiseerr = False 

1197 

1198 for ent in entities: 

1199 # return only the first _MapperEntity when searching 

1200 # based on string prop name. Ideally object 

1201 # attributes are used to specify more exactly. 

1202 return ent 

1203 else: 

1204 if raiseerr: 

1205 raise sa_exc.ArgumentError( 

1206 "Query has only expression-based entities - " 

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

1208 ) 

1209 else: 

1210 return None 

1211 

1212 

1213class loader_option(object): 

1214 def __init__(self): 

1215 pass 

1216 

1217 def __call__(self, fn): 

1218 self.name = name = fn.__name__ 

1219 self.fn = fn 

1220 if hasattr(Load, name): 

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

1222 setattr(Load, name, fn) 

1223 

1224 return self 

1225 

1226 def _add_unbound_fn(self, fn): 

1227 self._unbound_fn = fn 

1228 fn_doc = self.fn.__doc__ 

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

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

1231 

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

1233 

1234""" % { 

1235 "name": self.name 

1236 } 

1237 

1238 fn.__doc__ = fn_doc 

1239 return self 

1240 

1241 def _add_unbound_all_fn(self, fn): 

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

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

1244 

1245.. deprecated:: 0.9 

1246 

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

1248 in a future release. Please use method chaining with 

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

1250 

1251 session.query(MyClass).options( 

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

1253 ) 

1254 

1255""" % { 

1256 "name": self.name 

1257 } 

1258 fn = util.deprecated( 

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

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

1261 "0.9", 

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

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

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

1265 add_deprecation_to_docstring=False, 

1266 )(fn) 

1267 

1268 self._unbound_all_fn = fn 

1269 return self 

1270 

1271 

1272@loader_option() 

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

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

1275 columns stated manually in the query. 

1276 

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

1278 both method-chained and standalone operation. 

1279 

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

1281 the desired rows, i.e.:: 

1282 

1283 sess.query(Order).\ 

1284 join(Order.user).\ 

1285 options(contains_eager(Order.user)) 

1286 

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

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

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

1290 

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

1292 collection; queries will normally want to use the 

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

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

1295 

1296 sess.query(User).\ 

1297 join(User.addresses).\ 

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

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

1300 populate_existing() 

1301 

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

1303 

1304 .. seealso:: 

1305 

1306 :ref:`loading_toplevel` 

1307 

1308 :ref:`contains_eager` 

1309 

1310 """ 

1311 if alias is not None: 

1312 if not isinstance(alias, str): 

1313 info = inspect(alias) 

1314 alias = info.selectable 

1315 

1316 else: 

1317 util.warn_deprecated( 

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

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

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

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

1322 version="1.4", 

1323 ) 

1324 

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

1326 ot = inspect(attr._of_type) 

1327 alias = ot.selectable 

1328 

1329 cloned = loadopt.set_relationship_strategy( 

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

1331 ) 

1332 cloned.local_opts["eager_from_alias"] = alias 

1333 return cloned 

1334 

1335 

1336@contains_eager._add_unbound_fn 

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

1338 return _UnboundLoad()._from_keys( 

1339 _UnboundLoad.contains_eager, keys, True, kw 

1340 ) 

1341 

1342 

1343@loader_option() 

1344def load_only(loadopt, *attrs): 

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

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

1347 deferred. 

1348 

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

1350 both method-chained and standalone operation. 

1351 

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

1353 attributes:: 

1354 

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

1356 

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

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

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

1360 

1361 session.query(User).options( 

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

1363 ) 

1364 

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

1366 the lead entity can be 

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

1368 

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

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

1371 Load(Address).load_only(Address.email_address) 

1372 ) 

1373 

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

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

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

1377 

1378 .. versionadded:: 0.9.0 

1379 

1380 """ 

1381 cloned = loadopt.set_column_strategy( 

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

1383 ) 

1384 cloned.set_column_strategy( 

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

1386 ) 

1387 return cloned 

1388 

1389 

1390@load_only._add_unbound_fn 

1391def load_only(*attrs): 

1392 return _UnboundLoad().load_only(*attrs) 

1393 

1394 

1395@loader_option() 

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

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

1398 eager loading. 

1399 

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

1401 both method-chained and standalone operation. 

1402 

1403 examples:: 

1404 

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

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

1407 

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

1409 query(Order).options( 

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

1411 

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

1413 # joined-load the keywords collection 

1414 query(Order).options( 

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

1416 

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

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

1419 

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

1421 

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

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

1424 

1425 query(A).options( 

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

1427 joinedload(B.cs, innerjoin=True) 

1428 ) 

1429 

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

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

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

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

1434 

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

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

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

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

1439 is an outerjoin:: 

1440 

1441 query(A).options( 

1442 joinedload(A.bs). 

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

1444 ) 

1445 

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

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

1448 

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

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

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

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

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

1454 

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

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

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

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

1459 See :ref:`migration_3008`. 

1460 

1461 .. note:: 

1462 

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

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

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

1466 refer to these joins in any way, 

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

1468 detail. 

1469 

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

1471 :meth:`_query.Query.join`. 

1472 To combine explicit JOINs with eager loading 

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

1474 :ref:`contains_eager`. 

1475 

1476 .. seealso:: 

1477 

1478 :ref:`loading_toplevel` 

1479 

1480 :ref:`joined_eager_loading` 

1481 

1482 """ 

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

1484 if innerjoin is not None: 

1485 loader.local_opts["innerjoin"] = innerjoin 

1486 return loader 

1487 

1488 

1489@joinedload._add_unbound_fn 

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

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

1492 

1493 

1494@loader_option() 

1495def subqueryload(loadopt, attr): 

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

1497 subquery eager loading. 

1498 

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

1500 both method-chained and standalone operation. 

1501 

1502 examples:: 

1503 

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

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

1506 

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

1508 query(Order).options( 

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

1510 

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

1512 # subquery-load the keywords collection 

1513 query(Order).options( 

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

1515 

1516 

1517 .. seealso:: 

1518 

1519 :ref:`loading_toplevel` 

1520 

1521 :ref:`subquery_eager_loading` 

1522 

1523 """ 

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

1525 

1526 

1527@subqueryload._add_unbound_fn 

1528def subqueryload(*keys): 

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

1530 

1531 

1532@loader_option() 

1533def selectinload(loadopt, attr): 

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

1535 SELECT IN eager loading. 

1536 

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

1538 both method-chained and standalone operation. 

1539 

1540 examples:: 

1541 

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

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

1544 

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

1546 query(Order).options( 

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

1548 

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

1550 # selectin-load the keywords collection 

1551 query(Order).options( 

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

1553 

1554 .. versionadded:: 1.2 

1555 

1556 .. seealso:: 

1557 

1558 :ref:`loading_toplevel` 

1559 

1560 :ref:`selectin_eager_loading` 

1561 

1562 """ 

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

1564 

1565 

1566@selectinload._add_unbound_fn 

1567def selectinload(*keys): 

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

1569 

1570 

1571@loader_option() 

1572def lazyload(loadopt, attr): 

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

1574 loading. 

1575 

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

1577 both method-chained and standalone operation. 

1578 

1579 .. seealso:: 

1580 

1581 :ref:`loading_toplevel` 

1582 

1583 :ref:`lazy_loading` 

1584 

1585 """ 

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

1587 

1588 

1589@lazyload._add_unbound_fn 

1590def lazyload(*keys): 

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

1592 

1593 

1594@loader_option() 

1595def immediateload(loadopt, attr): 

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

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

1598 

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

1600 fire off any additional eager loaders. 

1601 

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

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

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

1605 

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

1607 both method-chained and standalone operation. 

1608 

1609 .. seealso:: 

1610 

1611 :ref:`loading_toplevel` 

1612 

1613 :ref:`selectin_eager_loading` 

1614 

1615 """ 

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

1617 return loader 

1618 

1619 

1620@immediateload._add_unbound_fn 

1621def immediateload(*keys): 

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

1623 

1624 

1625@loader_option() 

1626def noload(loadopt, attr): 

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

1628 

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

1630 producing any loading effect. 

1631 

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

1633 both method-chained and standalone operation. 

1634 

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

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

1637 

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

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

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

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

1642 

1643 .. seealso:: 

1644 

1645 :ref:`loading_toplevel` 

1646 

1647 """ 

1648 

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

1650 

1651 

1652@noload._add_unbound_fn 

1653def noload(*keys): 

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

1655 

1656 

1657@loader_option() 

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

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

1660 

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

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

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

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

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

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

1667 strategy will cause them to raise immediately. 

1668 

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

1670 attributes only. 

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

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

1673 loader option. 

1674 

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

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

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

1678 strategy will raise for all varieties of relationship loading. 

1679 

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

1681 both method-chained and standalone operation. 

1682 

1683 

1684 .. versionadded:: 1.1 

1685 

1686 .. seealso:: 

1687 

1688 :ref:`loading_toplevel` 

1689 

1690 :ref:`prevent_lazy_with_raiseload` 

1691 

1692 :ref:`deferred_raiseload` 

1693 

1694 """ 

1695 

1696 return loadopt.set_relationship_strategy( 

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

1698 ) 

1699 

1700 

1701@raiseload._add_unbound_fn 

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

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

1704 

1705 

1706@loader_option() 

1707def defaultload(loadopt, attr): 

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

1709 

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

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

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

1713 element of an element:: 

1714 

1715 session.query(MyClass).options( 

1716 defaultload(MyClass.someattribute). 

1717 joinedload(MyOtherClass.someotherattribute) 

1718 ) 

1719 

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

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

1722 

1723 session.query(MyClass).options( 

1724 defaultload(MyClass.someattribute). 

1725 defer("some_column"). 

1726 undefer("some_other_column") 

1727 ) 

1728 

1729 .. seealso:: 

1730 

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

1732 loader option structures with less verbosity than with individual 

1733 :func:`.defaultload` directives. 

1734 

1735 :ref:`relationship_loader_options` 

1736 

1737 :ref:`deferred_loading_w_multiple` 

1738 

1739 """ 

1740 return loadopt.set_relationship_strategy(attr, None) 

1741 

1742 

1743@defaultload._add_unbound_fn 

1744def defaultload(*keys): 

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

1746 

1747 

1748@loader_option() 

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

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

1751 e.g. not loaded until accessed. 

1752 

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

1754 both method-chained and standalone operation. 

1755 

1756 e.g.:: 

1757 

1758 from sqlalchemy.orm import defer 

1759 

1760 session.query(MyClass).options( 

1761 defer("attribute_one"), 

1762 defer("attribute_two")) 

1763 

1764 session.query(MyClass).options( 

1765 defer(MyClass.attribute_one), 

1766 defer(MyClass.attribute_two)) 

1767 

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

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

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

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

1772 

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

1774 

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

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

1777 each will operate on the same 

1778 parent entity:: 

1779 

1780 

1781 session.query(MyClass).options( 

1782 defaultload("someattr"). 

1783 defer("some_column"). 

1784 defer("some_other_column"). 

1785 defer("another_column") 

1786 ) 

1787 

1788 :param key: Attribute to be deferred. 

1789 

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

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

1792 SQL from being emitted. 

1793 

1794 .. versionadded:: 1.4 

1795 

1796 .. seealso:: 

1797 

1798 :ref:`deferred_raiseload` 

1799 

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

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

1802 by the method-chained style. 

1803 

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

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

1806 use method chaining in conjunction with defaultload() to 

1807 indicate a path. 

1808 

1809 

1810 .. seealso:: 

1811 

1812 :ref:`deferred` 

1813 

1814 :func:`_orm.undefer` 

1815 

1816 """ 

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

1818 if raiseload: 

1819 strategy["raiseload"] = True 

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

1821 

1822 

1823@defer._add_unbound_fn 

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

1825 if addl_attrs: 

1826 util.warn_deprecated( 

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

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

1829 "indicate a path.", 

1830 version="1.3", 

1831 ) 

1832 return _UnboundLoad._from_keys( 

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

1834 ) 

1835 

1836 

1837@loader_option() 

1838def undefer(loadopt, key): 

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

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

1841 

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

1843 :func:`.deferred` attribute. 

1844 

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

1846 both method-chained and standalone operation. 

1847 

1848 Examples:: 

1849 

1850 # undefer two columns 

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

1852 

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

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

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

1856 

1857 # undefer a column on a related object 

1858 session.query(MyClass).options( 

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

1860 

1861 :param key: Attribute to be undeferred. 

1862 

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

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

1865 by the method-chained style. 

1866 

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

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

1869 use method chaining in conjunction with defaultload() to 

1870 indicate a path. 

1871 

1872 .. seealso:: 

1873 

1874 :ref:`deferred` 

1875 

1876 :func:`_orm.defer` 

1877 

1878 :func:`_orm.undefer_group` 

1879 

1880 """ 

1881 return loadopt.set_column_strategy( 

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

1883 ) 

1884 

1885 

1886@undefer._add_unbound_fn 

1887def undefer(key, *addl_attrs): 

1888 if addl_attrs: 

1889 util.warn_deprecated( 

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

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

1892 "indicate a path.", 

1893 version="1.3", 

1894 ) 

1895 return _UnboundLoad._from_keys( 

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

1897 ) 

1898 

1899 

1900@loader_option() 

1901def undefer_group(loadopt, name): 

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

1903 undeferred. 

1904 

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

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

1907 

1908 E.g:: 

1909 

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

1911 

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

1913 spelled out using relationship loader options, such as 

1914 :func:`_orm.defaultload`:: 

1915 

1916 session.query(MyClass).options( 

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

1918 

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

1920 particular entity load path. 

1921 

1922 .. seealso:: 

1923 

1924 :ref:`deferred` 

1925 

1926 :func:`_orm.defer` 

1927 

1928 :func:`_orm.undefer` 

1929 

1930 """ 

1931 return loadopt.set_column_strategy( 

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

1933 ) 

1934 

1935 

1936@undefer_group._add_unbound_fn 

1937def undefer_group(name): 

1938 return _UnboundLoad().undefer_group(name) 

1939 

1940 

1941@loader_option() 

1942def with_expression(loadopt, key, expression): 

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

1944 

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

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

1947 target of an ad-hoc SQL expression. 

1948 

1949 E.g.:: 

1950 

1951 

1952 sess.query(SomeClass).options( 

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

1954 ) 

1955 

1956 .. versionadded:: 1.2 

1957 

1958 :param key: Attribute to be undeferred. 

1959 

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

1961 

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

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

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

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

1966 usage details. 

1967 

1968 .. seealso:: 

1969 

1970 :ref:`mapper_querytime_expression` 

1971 

1972 """ 

1973 

1974 expression = coercions.expect( 

1975 roles.LabeledColumnExprRole, _orm_full_deannotate(expression) 

1976 ) 

1977 

1978 return loadopt.set_column_strategy( 

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

1980 ) 

1981 

1982 

1983@with_expression._add_unbound_fn 

1984def with_expression(key, expression): 

1985 return _UnboundLoad._from_keys( 

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

1987 ) 

1988 

1989 

1990@loader_option() 

1991def selectin_polymorphic(loadopt, classes): 

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

1993 specific to a subclass. 

1994 

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

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

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

1998 

1999 .. versionadded:: 1.2 

2000 

2001 .. seealso:: 

2002 

2003 :ref:`polymorphic_selectin` 

2004 

2005 """ 

2006 loadopt.set_class_strategy( 

2007 {"selectinload_polymorphic": True}, 

2008 opts={ 

2009 "entities": tuple( 

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

2011 ) 

2012 }, 

2013 ) 

2014 return loadopt 

2015 

2016 

2017@selectin_polymorphic._add_unbound_fn 

2018def selectin_polymorphic(base_cls, classes): 

2019 ul = _UnboundLoad() 

2020 ul.is_class_strategy = True 

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

2022 ul.selectin_polymorphic(classes) 

2023 return ul