Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors 

2# <see AUTHORS file> 

3# 

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

5# the MIT License: http://www.opensource.org/licenses/mit-license.php 

6 

7""" 

8 

9""" 

10 

11from . 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 MapperOption 

18from .interfaces import PropComparator 

19from .path_registry import _DEFAULT_TOKEN 

20from .path_registry import _WILDCARD_TOKEN 

21from .path_registry import PathRegistry 

22from .path_registry import TokenRegistry 

23from .util import _orm_full_deannotate 

24from .. import exc as sa_exc 

25from .. import inspect 

26from .. import util 

27from ..sql import expression as sql_expr 

28from ..sql.base import _generative 

29from ..sql.base import Generative 

30 

31 

32class Load(Generative, MapperOption): 

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

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

35 loaded. 

36 

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

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

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

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

41 

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

43 class as the argument. This style of usage is 

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

45 that has multiple entities:: 

46 

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

48 

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

50 where it 

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

52 

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

54 

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

56 is useful as public API is when specifying 

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

58 

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

60 

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

62 attributes on those descendant objects will load using their normal 

63 loader strategy. 

64 

65 .. seealso:: 

66 

67 :ref:`deferred_options` 

68 

69 :ref:`deferred_loading_w_multiple` 

70 

71 :ref:`relationship_loader_options` 

72 

73 """ 

74 

75 def __init__(self, entity): 

76 insp = inspect(entity) 

77 self.path = insp._path_registry 

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

79 # Load objects 

80 self.context = util.OrderedDict() 

81 self.local_opts = {} 

82 self.is_class_strategy = False 

83 

84 @classmethod 

85 def for_existing_path(cls, path): 

86 load = cls.__new__(cls) 

87 load.path = path 

88 load.context = {} 

89 load.local_opts = {} 

90 load._of_type = None 

91 return load 

92 

93 def _generate_cache_key(self, path): 

94 if path.path[0].is_aliased_class: 

95 return False 

96 

97 serialized = [] 

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

99 if key != "loader": 

100 continue 

101 

102 for local_elem, obj_elem in zip(self.path.path, loader_path): 

103 if local_elem is not obj_elem: 

104 break 

105 else: 

106 endpoint = obj._of_type or obj.path.path[-1] 

107 chopped = self._chop_path(loader_path, path) 

108 

109 if ( 

110 # means loader_path and path are unrelated, 

111 # this does not need to be part of a cache key 

112 chopped 

113 is None 

114 ) or ( 

115 # means no additional path with loader_path + path 

116 # and the endpoint isn't using of_type so isn't modified 

117 # into an alias or other unsafe entity 

118 not chopped 

119 and not obj._of_type 

120 ): 

121 continue 

122 

123 serialized_path = [] 

124 

125 for token in chopped: 

126 if isinstance(token, util.string_types): 

127 serialized_path.append(token) 

128 elif token.is_aliased_class: 

129 return False 

130 elif token.is_property: 

131 serialized_path.append(token.key) 

132 else: 

133 assert token.is_mapper 

134 serialized_path.append(token.class_) 

135 

136 if not serialized_path or endpoint != serialized_path[-1]: 

137 if endpoint.is_mapper: 

138 serialized_path.append(endpoint.class_) 

139 elif endpoint.is_aliased_class: 

140 return False 

141 

142 serialized.append( 

143 ( 

144 tuple(serialized_path) 

145 + (obj.strategy or ()) 

146 + ( 

147 tuple( 

148 [ 

149 (key, obj.local_opts[key]) 

150 for key in sorted(obj.local_opts) 

151 ] 

152 ) 

153 if obj.local_opts 

154 else () 

155 ) 

156 ) 

157 ) 

158 if not serialized: 

159 return None 

160 else: 

161 return tuple(serialized) 

162 

163 def _generate(self): 

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

165 cloned.local_opts = {} 

166 return cloned 

167 

168 is_opts_only = False 

169 is_class_strategy = False 

170 strategy = None 

171 propagate_to_loaders = False 

172 _of_type = None 

173 

174 def process_query(self, query): 

175 self._process(query, True) 

176 

177 def process_query_conditionally(self, query): 

178 self._process(query, False) 

179 

180 def _process(self, query, raiseerr): 

181 current_path = query._current_path 

182 if current_path: 

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

184 chopped_start_path = self._chop_path(start_path, current_path) 

185 if chopped_start_path is not None: 

186 query._attributes[(token, chopped_start_path)] = loader 

187 else: 

188 query._attributes.update(self.context) 

189 

190 def _generate_path( 

191 self, path, attr, for_strategy, wildcard_key, raiseerr=True 

192 ): 

193 existing_of_type = self._of_type 

194 self._of_type = None 

195 if raiseerr and not path.has_entity: 

196 if isinstance(path, TokenRegistry): 

197 raise sa_exc.ArgumentError( 

198 "Wildcard token cannot be followed by another entity" 

199 ) 

200 else: 

201 raise sa_exc.ArgumentError( 

202 "Mapped attribute '%s' does not " 

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

204 ) 

205 

206 if isinstance(attr, util.string_types): 

207 default_token = attr.endswith(_DEFAULT_TOKEN) 

208 if attr.endswith(_WILDCARD_TOKEN) or default_token: 

209 if default_token: 

210 self.propagate_to_loaders = False 

211 if wildcard_key: 

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

213 

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

215 # working for a with_polymorphic entity because the 

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

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

218 if existing_of_type and not existing_of_type.is_aliased_class: 

219 path = path.parent[existing_of_type] 

220 path = path.token(attr) 

221 self.path = path 

222 return path 

223 

224 if existing_of_type: 

225 ent = inspect(existing_of_type) 

226 else: 

227 ent = path.entity 

228 

229 try: 

230 # use getattr on the class to work around 

231 # synonyms, hybrids, etc. 

232 attr = getattr(ent.class_, attr) 

233 except AttributeError as err: 

234 if raiseerr: 

235 util.raise_( 

236 sa_exc.ArgumentError( 

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

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

239 ), 

240 replace_context=err, 

241 ) 

242 else: 

243 return None 

244 else: 

245 attr = found_property = attr.property 

246 

247 path = path[attr] 

248 elif _is_mapped_class(attr): 

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

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

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

252 if not orm_util._entity_corresponds_to_use_path_impl( 

253 attr.parent, path[-1] 

254 ): 

255 if raiseerr: 

256 raise sa_exc.ArgumentError( 

257 "Attribute '%s' does not " 

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

259 ) 

260 else: 

261 return None 

262 else: 

263 prop = found_property = attr.property 

264 

265 if not orm_util._entity_corresponds_to_use_path_impl( 

266 attr.parent, path[-1] 

267 ): 

268 if raiseerr: 

269 raise sa_exc.ArgumentError( 

270 'Attribute "%s" does not ' 

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

272 % ( 

273 attr, 

274 path.entity, 

275 ( 

276 " Did you mean to use " 

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

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

279 if len(path) > 1 

280 and path.entity.is_mapper 

281 and attr.parent.is_aliased_class 

282 else "" 

283 ), 

284 ) 

285 ) 

286 else: 

287 return None 

288 

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

290 ac = attr._of_type 

291 ext_info = of_type_info = inspect(ac) 

292 

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

294 self.context, "path_with_polymorphic" 

295 ) 

296 

297 if not ext_info.is_aliased_class: 

298 ac = orm_util.with_polymorphic( 

299 ext_info.mapper.base_mapper, 

300 ext_info.mapper, 

301 aliased=True, 

302 _use_mapper_path=True, 

303 _existing_alias=inspect(existing) 

304 if existing is not None 

305 else None, 

306 ) 

307 

308 ext_info = inspect(ac) 

309 

310 path.entity_path[prop].set( 

311 self.context, "path_with_polymorphic", ac 

312 ) 

313 

314 path = path[prop][ext_info] 

315 

316 self._of_type = of_type_info 

317 

318 else: 

319 path = path[prop] 

320 

321 if for_strategy is not None: 

322 found_property._get_strategy(for_strategy) 

323 if path.has_entity: 

324 path = path.entity_path 

325 self.path = path 

326 return path 

327 

328 def __str__(self): 

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

330 

331 def _coerce_strat(self, strategy): 

332 if strategy is not None: 

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

334 return strategy 

335 

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

337 raise NotImplementedError( 

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

339 "Load.options() method" 

340 ) 

341 

342 @_generative 

343 def options(self, *opts): 

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

345 :class:`_orm.Load` 

346 object. 

347 

348 E.g.:: 

349 

350 query = session.query(Author) 

351 query = query.options( 

352 joinedload(Author.book).options( 

353 load_only("summary", "excerpt"), 

354 joinedload(Book.citations).options( 

355 joinedload(Citation.author) 

356 ) 

357 ) 

358 ) 

359 

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

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

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

363 

364 .. versionadded:: 1.3.6 

365 

366 .. seealso:: 

367 

368 :func:`.defaultload` 

369 

370 :ref:`relationship_loader_options` 

371 

372 :ref:`deferred_loading_w_multiple` 

373 

374 """ 

375 apply_cache = {} 

376 bound = not isinstance(self, _UnboundLoad) 

377 if bound: 

378 raise NotImplementedError( 

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

380 "for 'unbound' loader options" 

381 ) 

382 for opt in opts: 

383 opt._apply_to_parent(self, apply_cache, bound) 

384 

385 @_generative 

386 def set_relationship_strategy( 

387 self, attr, strategy, propagate_to_loaders=True 

388 ): 

389 strategy = self._coerce_strat(strategy) 

390 

391 self.propagate_to_loaders = propagate_to_loaders 

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

393 self.path = cloned.path 

394 self._of_type = cloned._of_type 

395 cloned.is_class_strategy = self.is_class_strategy = False 

396 self.propagate_to_loaders = cloned.propagate_to_loaders 

397 

398 @_generative 

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

400 strategy = self._coerce_strat(strategy) 

401 

402 self.is_class_strategy = False 

403 for attr in attrs: 

404 cloned = self._clone_for_bind_strategy( 

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

406 ) 

407 cloned.propagate_to_loaders = True 

408 

409 @_generative 

410 def set_generic_strategy(self, attrs, strategy): 

411 strategy = self._coerce_strat(strategy) 

412 

413 for attr in attrs: 

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

415 cloned.propagate_to_loaders = True 

416 

417 @_generative 

418 def set_class_strategy(self, strategy, opts): 

419 strategy = self._coerce_strat(strategy) 

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

421 cloned.is_class_strategy = True 

422 cloned.propagate_to_loaders = True 

423 cloned.local_opts.update(opts) 

424 

425 def _clone_for_bind_strategy( 

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

427 ): 

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

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

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

431 in order to not create reference cycles. 

432 

433 """ 

434 cloned = self._generate() 

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

436 cloned.strategy = strategy 

437 

438 cloned.local_opts = self.local_opts 

439 if opts: 

440 cloned.local_opts.update(opts) 

441 if opts_only: 

442 cloned.is_opts_only = True 

443 

444 if strategy or cloned.is_opts_only: 

445 cloned._set_path_strategy() 

446 return cloned 

447 

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

449 if merge_opts or not replace: 

450 existing = path.get(self.context, "loader") 

451 

452 if existing: 

453 if merge_opts: 

454 existing.local_opts.update(self.local_opts) 

455 else: 

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

457 else: 

458 existing = path.get(self.context, "loader") 

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

460 if existing and existing.is_opts_only: 

461 self.local_opts.update(existing.local_opts) 

462 

463 def _set_path_strategy(self): 

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

465 effective_path = self.path.parent 

466 else: 

467 effective_path = self.path 

468 

469 if effective_path.is_token: 

470 for path in effective_path.generate_for_superclasses(): 

471 self._set_for_path( 

472 self.context, 

473 path, 

474 replace=True, 

475 merge_opts=self.is_opts_only, 

476 ) 

477 else: 

478 self._set_for_path( 

479 self.context, 

480 effective_path, 

481 replace=True, 

482 merge_opts=self.is_opts_only, 

483 ) 

484 

485 # remove cycles; _set_path_strategy is always invoked on an 

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

487 self.context = None 

488 

489 def __getstate__(self): 

490 d = self.__dict__.copy() 

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

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

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

494 ) 

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

496 return d 

497 

498 def __setstate__(self, state): 

499 self.__dict__.update(state) 

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

501 if self.context is not None: 

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

503 

504 def _chop_path(self, to_chop, path): 

505 i = -1 

506 

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

508 if isinstance(c_token, util.string_types): 

509 # TODO: this is approximated from the _UnboundLoad 

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

511 

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

513 return to_chop 

514 elif ( 

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

516 and c_token != p_token.key 

517 ): 

518 return None 

519 

520 if c_token is p_token: 

521 continue 

522 elif ( 

523 isinstance(c_token, InspectionAttr) 

524 and c_token.is_mapper 

525 and p_token.is_mapper 

526 and c_token.isa(p_token) 

527 ): 

528 continue 

529 else: 

530 return None 

531 return to_chop[i + 1 :] 

532 

533 

534class _UnboundLoad(Load): 

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

536 

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

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

539 

540 This provides compatibility with the traditional system 

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

542 

543 """ 

544 

545 def __init__(self): 

546 self.path = () 

547 self._to_bind = [] 

548 self.local_opts = {} 

549 

550 _is_chain_link = False 

551 

552 def _generate_cache_key(self, path): 

553 serialized = () 

554 for val in self._to_bind: 

555 for local_elem, val_elem in zip(self.path, val.path): 

556 if local_elem is not val_elem: 

557 break 

558 else: 

559 opt = val._bind_loader([path.path[0]], None, None, False) 

560 if opt: 

561 c_key = opt._generate_cache_key(path) 

562 if c_key is False: 

563 return False 

564 elif c_key: 

565 serialized += c_key 

566 if not serialized: 

567 return None 

568 else: 

569 return serialized 

570 

571 def _set_path_strategy(self): 

572 self._to_bind.append(self) 

573 

574 # remove cycles; _set_path_strategy is always invoked on an 

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

576 self._to_bind = None 

577 

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

579 if self in applied: 

580 return applied[self] 

581 

582 if to_bind is None: 

583 to_bind = self._to_bind 

584 

585 cloned = self._generate() 

586 

587 applied[self] = cloned 

588 

589 cloned.strategy = self.strategy 

590 if self.path: 

591 attr = self.path[-1] 

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

593 _DEFAULT_TOKEN 

594 ): 

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

596 cloned._generate_path( 

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

598 ) 

599 

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

601 # mature 

602 assert cloned.propagate_to_loaders == self.propagate_to_loaders 

603 assert cloned.is_class_strategy == self.is_class_strategy 

604 assert cloned.is_opts_only == self.is_opts_only 

605 

606 new_to_bind = { 

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

608 for elem in to_bind 

609 } 

610 cloned._to_bind = parent._to_bind 

611 cloned._to_bind.extend(new_to_bind) 

612 cloned.local_opts.update(self.local_opts) 

613 

614 return cloned 

615 

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

617 if ( 

618 wildcard_key 

619 and isinstance(attr, util.string_types) 

620 and attr in (_WILDCARD_TOKEN, _DEFAULT_TOKEN) 

621 ): 

622 if attr == _DEFAULT_TOKEN: 

623 self.propagate_to_loaders = False 

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

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

626 path = path[0:-1] 

627 if attr: 

628 path = path + (attr,) 

629 self.path = path 

630 return path 

631 

632 def __getstate__(self): 

633 d = self.__dict__.copy() 

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

635 return d 

636 

637 def __setstate__(self, state): 

638 ret = [] 

639 for key in state["path"]: 

640 if isinstance(key, tuple): 

641 if len(key) == 2: 

642 # support legacy 

643 cls, propkey = key 

644 of_type = None 

645 else: 

646 cls, propkey, of_type = key 

647 prop = getattr(cls, propkey) 

648 if of_type: 

649 prop = prop.of_type(of_type) 

650 ret.append(prop) 

651 else: 

652 ret.append(key) 

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

654 self.__dict__ = state 

655 

656 def _process(self, query, raiseerr): 

657 dedupes = query._attributes["_unbound_load_dedupes"] 

658 for val in self._to_bind: 

659 if val not in dedupes: 

660 dedupes.add(val) 

661 val._bind_loader( 

662 [ent.entity_zero for ent in query._mapper_entities], 

663 query._current_path, 

664 query._attributes, 

665 raiseerr, 

666 ) 

667 

668 @classmethod 

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

670 opt = _UnboundLoad() 

671 

672 def _split_key(key): 

673 if isinstance(key, util.string_types): 

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

675 if key == _WILDCARD_TOKEN: 

676 return (_DEFAULT_TOKEN,) 

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

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

679 key = key[1:] 

680 return key.split(".") 

681 else: 

682 return (key,) 

683 

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

685 

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

687 # set _is_chain_link first so that clones of the 

688 # object also inherit this flag 

689 opt._is_chain_link = True 

690 if chained: 

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

692 else: 

693 opt = opt.defaultload(token) 

694 

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

696 opt._is_chain_link = False 

697 

698 return opt 

699 

700 def _chop_path(self, to_chop, path): 

701 i = -1 

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

703 zip(to_chop, path.pairs()) 

704 ): 

705 if isinstance(c_token, util.string_types): 

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

707 return to_chop 

708 elif ( 

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

710 and c_token != p_prop.key 

711 ): 

712 return None 

713 elif isinstance(c_token, PropComparator): 

714 if c_token.property is not p_prop or ( 

715 c_token._parententity is not p_entity 

716 and ( 

717 not c_token._parententity.is_mapper 

718 or not c_token._parententity.isa(p_entity) 

719 ) 

720 ): 

721 return None 

722 else: 

723 i += 1 

724 

725 return to_chop[i:] 

726 

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

728 ret = [] 

729 for token in path: 

730 if isinstance(token, QueryableAttribute): 

731 if ( 

732 filter_aliased_class 

733 and token._of_type 

734 and inspect(token._of_type).is_aliased_class 

735 ): 

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

737 else: 

738 ret.append( 

739 (token._parentmapper.class_, token.key, token._of_type) 

740 ) 

741 elif isinstance(token, PropComparator): 

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

743 else: 

744 ret.append(token) 

745 return ret 

746 

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

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

749 

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

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

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

753 given Query into a Load. 

754 

755 Example:: 

756 

757 

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

759 joinedload("orders").joinedload("items")) 

760 

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

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

763 

764 _UnboundLoad( 

765 _to_bind=[ 

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

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

768 ] 

769 ) 

770 

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

772 not exact API):: 

773 

774 Load( 

775 User, 

776 (User, User.orders.property)) 

777 Load( 

778 User, 

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

780 

781 """ 

782 

783 start_path = self.path 

784 

785 if self.is_class_strategy and current_path: 

786 start_path += (entities[0],) 

787 

788 # _current_path implies we're in a 

789 # secondary load with an existing path 

790 

791 if current_path: 

792 start_path = self._chop_path(start_path, current_path) 

793 

794 if not start_path: 

795 return None 

796 

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

798 # what entity we are referring towards. 

799 token = start_path[0] 

800 

801 if isinstance(token, util.string_types): 

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

803 elif isinstance(token, PropComparator): 

804 prop = token.property 

805 entity = self._find_entity_prop_comparator( 

806 entities, prop, token._parententity, raiseerr 

807 ) 

808 elif self.is_class_strategy and _is_mapped_class(token): 

809 entity = inspect(token) 

810 if entity not in entities: 

811 entity = None 

812 else: 

813 raise sa_exc.ArgumentError( 

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

815 ) 

816 

817 if not entity: 

818 return 

819 

820 path_element = entity 

821 

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

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

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

825 # tokens and populate into the Load(). 

826 loader = Load(path_element) 

827 if context is not None: 

828 loader.context = context 

829 else: 

830 context = loader.context 

831 

832 loader.strategy = self.strategy 

833 loader.is_opts_only = self.is_opts_only 

834 loader.is_class_strategy = self.is_class_strategy 

835 

836 path = loader.path 

837 

838 if not loader.is_class_strategy: 

839 for idx, token in enumerate(start_path): 

840 if not loader._generate_path( 

841 loader.path, 

842 token, 

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

844 None, 

845 raiseerr, 

846 ): 

847 return 

848 

849 loader.local_opts.update(self.local_opts) 

850 

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

852 effective_path = loader.path.parent 

853 else: 

854 effective_path = loader.path 

855 

856 # prioritize "first class" options over those 

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

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

859 

860 if effective_path.is_token: 

861 for path in effective_path.generate_for_superclasses(): 

862 loader._set_for_path( 

863 context, 

864 path, 

865 replace=not self._is_chain_link, 

866 merge_opts=self.is_opts_only, 

867 ) 

868 else: 

869 loader._set_for_path( 

870 context, 

871 effective_path, 

872 replace=not self._is_chain_link, 

873 merge_opts=self.is_opts_only, 

874 ) 

875 

876 return loader 

877 

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

879 if _is_aliased_class(mapper): 

880 searchfor = mapper 

881 else: 

882 searchfor = _class_to_mapper(mapper) 

883 for ent in entities: 

884 if orm_util._entity_corresponds_to(ent, searchfor): 

885 return ent 

886 else: 

887 if raiseerr: 

888 if not list(entities): 

889 raise sa_exc.ArgumentError( 

890 "Query has only expression-based entities, " 

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

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

893 ) 

894 else: 

895 raise sa_exc.ArgumentError( 

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

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

898 "specify the full path " 

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

900 "attribute. " 

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

902 ) 

903 else: 

904 return None 

905 

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

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

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

909 if raiseerr: 

910 raise sa_exc.ArgumentError( 

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

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

913 "loader options for each entity individually, such " 

914 "as %s." 

915 % ( 

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

917 ", ".join( 

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

919 for ent in entities 

920 ), 

921 ) 

922 ) 

923 elif token.endswith(_DEFAULT_TOKEN): 

924 raiseerr = False 

925 

926 for ent in entities: 

927 # return only the first _MapperEntity when searching 

928 # based on string prop name. Ideally object 

929 # attributes are used to specify more exactly. 

930 return ent 

931 else: 

932 if raiseerr: 

933 raise sa_exc.ArgumentError( 

934 "Query has only expression-based entities - " 

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

936 ) 

937 else: 

938 return None 

939 

940 

941class loader_option(object): 

942 def __init__(self): 

943 pass 

944 

945 def __call__(self, fn): 

946 self.name = name = fn.__name__ 

947 self.fn = fn 

948 if hasattr(Load, name): 

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

950 setattr(Load, name, fn) 

951 

952 return self 

953 

954 def _add_unbound_fn(self, fn): 

955 self._unbound_fn = fn 

956 fn_doc = self.fn.__doc__ 

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

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

959 

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

961 

962""" % { 

963 "name": self.name 

964 } 

965 

966 fn.__doc__ = fn_doc 

967 return self 

968 

969 def _add_unbound_all_fn(self, fn): 

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

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

972 

973.. deprecated:: 0.9 

974 

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

976 in a future release. Please use method chaining with 

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

978 

979 session.query(MyClass).options( 

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

981 ) 

982 

983""" % { 

984 "name": self.name 

985 } 

986 fn = util.deprecated( 

987 "0.9", 

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

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

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

991 add_deprecation_to_docstring=False, 

992 )(fn) 

993 

994 self._unbound_all_fn = fn 

995 return self 

996 

997 

998@loader_option() 

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

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

1001 columns stated manually in the query. 

1002 

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

1004 both method-chained and standalone operation. 

1005 

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

1007 the desired rows, i.e.:: 

1008 

1009 sess.query(Order).\ 

1010 join(Order.user).\ 

1011 options(contains_eager(Order.user)) 

1012 

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

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

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

1016 

1017 When making use of aliases with :func:`.contains_eager`, the path 

1018 should be specified using :meth:`.PropComparator.of_type`:: 

1019 

1020 user_alias = aliased(User) 

1021 sess.query(Order).\ 

1022 join((user_alias, Order.user)).\ 

1023 options(contains_eager(Order.user.of_type(user_alias))) 

1024 

1025 :meth:`.PropComparator.of_type` is also used to indicate a join 

1026 against specific subclasses of an inherting mapper, or 

1027 of a :func:`.with_polymorphic` construct:: 

1028 

1029 # employees of a particular subtype 

1030 sess.query(Company).\ 

1031 outerjoin(Company.employees.of_type(Manager)).\ 

1032 options( 

1033 contains_eager( 

1034 Company.employees.of_type(Manager), 

1035 ) 

1036 ) 

1037 

1038 # employees of a multiple subtypes 

1039 wp = with_polymorphic(Employee, [Manager, Engineer]) 

1040 sess.query(Company).\ 

1041 outerjoin(Company.employees.of_type(wp)).\ 

1042 options( 

1043 contains_eager( 

1044 Company.employees.of_type(wp), 

1045 ) 

1046 ) 

1047 

1048 The :paramref:`.contains_eager.alias` parameter is used for a similar 

1049 purpose, however the :meth:`.PropComparator.of_type` approach should work 

1050 in all cases and is more effective and explicit. 

1051 

1052 .. seealso:: 

1053 

1054 :ref:`loading_toplevel` 

1055 

1056 :ref:`contains_eager` 

1057 

1058 """ 

1059 if alias is not None: 

1060 if not isinstance(alias, str): 

1061 info = inspect(alias) 

1062 alias = info.selectable 

1063 

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

1065 ot = inspect(attr._of_type) 

1066 alias = ot.selectable 

1067 

1068 cloned = loadopt.set_relationship_strategy( 

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

1070 ) 

1071 cloned.local_opts["eager_from_alias"] = alias 

1072 return cloned 

1073 

1074 

1075@contains_eager._add_unbound_fn 

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

1077 return _UnboundLoad()._from_keys( 

1078 _UnboundLoad.contains_eager, keys, True, kw 

1079 ) 

1080 

1081 

1082@loader_option() 

1083def load_only(loadopt, *attrs): 

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

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

1086 deferred. 

1087 

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

1089 both method-chained and standalone operation. 

1090 

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

1092 attributes:: 

1093 

1094 session.query(User).options(load_only("name", "fullname")) 

1095 

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

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

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

1099 

1100 session.query(User).options( 

1101 subqueryload("addresses").load_only("email_address") 

1102 ) 

1103 

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

1105 the lead entity can be 

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

1107 

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

1109 Load(User).load_only("name", "fullname"), 

1110 Load(Address).load_only("email_address") 

1111 ) 

1112 

1113 

1114 .. versionadded:: 0.9.0 

1115 

1116 """ 

1117 cloned = loadopt.set_column_strategy( 

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

1119 ) 

1120 cloned.set_column_strategy( 

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

1122 ) 

1123 return cloned 

1124 

1125 

1126@load_only._add_unbound_fn 

1127def load_only(*attrs): 

1128 return _UnboundLoad().load_only(*attrs) 

1129 

1130 

1131@loader_option() 

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

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

1134 eager loading. 

1135 

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

1137 both method-chained and standalone operation. 

1138 

1139 examples:: 

1140 

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

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

1143 

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

1145 query(Order).options( 

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

1147 

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

1149 # joined-load the keywords collection 

1150 query(Order).options( 

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

1152 

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

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

1155 

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

1157 

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

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

1160 

1161 query(A).options( 

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

1163 joinedload(B.cs, innerjoin=True) 

1164 ) 

1165 

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

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

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

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

1170 

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

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

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

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

1175 is an outerjoin:: 

1176 

1177 query(A).options( 

1178 joinedload(A.bs). 

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

1180 ) 

1181 

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

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

1184 

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

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

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

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

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

1190 

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

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

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

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

1195 See :ref:`migration_3008`. 

1196 

1197 .. note:: 

1198 

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

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

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

1202 refer to these joins in any way, 

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

1204 detail. 

1205 

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

1207 :meth:`_query.Query.join`. 

1208 To combine explicit JOINs with eager loading 

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

1210 :ref:`contains_eager`. 

1211 

1212 .. seealso:: 

1213 

1214 :ref:`loading_toplevel` 

1215 

1216 :ref:`joined_eager_loading` 

1217 

1218 """ 

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

1220 if innerjoin is not None: 

1221 loader.local_opts["innerjoin"] = innerjoin 

1222 return loader 

1223 

1224 

1225@joinedload._add_unbound_fn 

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

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

1228 

1229 

1230@joinedload._add_unbound_all_fn 

1231def joinedload_all(*keys, **kw): 

1232 return _UnboundLoad._from_keys(_UnboundLoad.joinedload, keys, True, kw) 

1233 

1234 

1235@loader_option() 

1236def subqueryload(loadopt, attr): 

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

1238 subquery eager loading. 

1239 

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

1241 both method-chained and standalone operation. 

1242 

1243 examples:: 

1244 

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

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

1247 

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

1249 query(Order).options( 

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

1251 

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

1253 # subquery-load the keywords collection 

1254 query(Order).options( 

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

1256 

1257 

1258 .. seealso:: 

1259 

1260 :ref:`loading_toplevel` 

1261 

1262 :ref:`subquery_eager_loading` 

1263 

1264 """ 

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

1266 

1267 

1268@subqueryload._add_unbound_fn 

1269def subqueryload(*keys): 

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

1271 

1272 

1273@subqueryload._add_unbound_all_fn 

1274def subqueryload_all(*keys): 

1275 return _UnboundLoad._from_keys(_UnboundLoad.subqueryload, keys, True, {}) 

1276 

1277 

1278@loader_option() 

1279def selectinload(loadopt, attr): 

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

1281 SELECT IN eager loading. 

1282 

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

1284 both method-chained and standalone operation. 

1285 

1286 examples:: 

1287 

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

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

1290 

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

1292 query(Order).options( 

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

1294 

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

1296 # selectin-load the keywords collection 

1297 query(Order).options( 

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

1299 

1300 .. versionadded:: 1.2 

1301 

1302 .. seealso:: 

1303 

1304 :ref:`loading_toplevel` 

1305 

1306 :ref:`selectin_eager_loading` 

1307 

1308 """ 

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

1310 

1311 

1312@selectinload._add_unbound_fn 

1313def selectinload(*keys): 

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

1315 

1316 

1317@selectinload._add_unbound_all_fn 

1318def selectinload_all(*keys): 

1319 return _UnboundLoad._from_keys(_UnboundLoad.selectinload, keys, True, {}) 

1320 

1321 

1322@loader_option() 

1323def lazyload(loadopt, attr): 

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

1325 loading. 

1326 

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

1328 both method-chained and standalone operation. 

1329 

1330 .. seealso:: 

1331 

1332 :ref:`loading_toplevel` 

1333 

1334 :ref:`lazy_loading` 

1335 

1336 """ 

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

1338 

1339 

1340@lazyload._add_unbound_fn 

1341def lazyload(*keys): 

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

1343 

1344 

1345@lazyload._add_unbound_all_fn 

1346def lazyload_all(*keys): 

1347 return _UnboundLoad._from_keys(_UnboundLoad.lazyload, keys, True, {}) 

1348 

1349 

1350@loader_option() 

1351def immediateload(loadopt, attr): 

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

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

1354 

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

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

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

1358 

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

1360 both method-chained and standalone operation. 

1361 

1362 .. seealso:: 

1363 

1364 :ref:`loading_toplevel` 

1365 

1366 :ref:`selectin_eager_loading` 

1367 

1368 """ 

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

1370 return loader 

1371 

1372 

1373@immediateload._add_unbound_fn 

1374def immediateload(*keys): 

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

1376 

1377 

1378@loader_option() 

1379def noload(loadopt, attr): 

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

1381 

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

1383 both method-chained and standalone operation. 

1384 

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

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

1387 

1388 .. seealso:: 

1389 

1390 :ref:`loading_toplevel` 

1391 

1392 """ 

1393 

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

1395 

1396 

1397@noload._add_unbound_fn 

1398def noload(*keys): 

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

1400 

1401 

1402@loader_option() 

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

1404 """Indicate that the given relationship attribute should disallow lazy loads. 

1405 

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

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

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

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

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

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

1412 strategy will cause them to raise immediately. 

1413 

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

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

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

1417 the strategy will raise for all varieties of lazyload. 

1418 

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

1420 both method-chained and standalone operation. 

1421 

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

1423 attributes only. 

1424 

1425 .. versionadded:: 1.1 

1426 

1427 .. seealso:: 

1428 

1429 :ref:`loading_toplevel` 

1430 

1431 :ref:`prevent_lazy_with_raiseload` 

1432 

1433 """ 

1434 

1435 return loadopt.set_relationship_strategy( 

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

1437 ) 

1438 

1439 

1440@raiseload._add_unbound_fn 

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

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

1443 

1444 

1445@loader_option() 

1446def defaultload(loadopt, attr): 

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

1448 

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

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

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

1452 element of an element:: 

1453 

1454 session.query(MyClass).options( 

1455 defaultload(MyClass.someattribute). 

1456 joinedload(MyOtherClass.someotherattribute) 

1457 ) 

1458 

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

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

1461 

1462 session.query(MyClass).options( 

1463 defaultload(MyClass.someattribute). 

1464 defer("some_column"). 

1465 undefer("some_other_column") 

1466 ) 

1467 

1468 .. seealso:: 

1469 

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

1471 loader option structures with less verbosity than with individual 

1472 :func:`.defaultload` directives. 

1473 

1474 :ref:`relationship_loader_options` 

1475 

1476 :ref:`deferred_loading_w_multiple` 

1477 

1478 """ 

1479 return loadopt.set_relationship_strategy(attr, None) 

1480 

1481 

1482@defaultload._add_unbound_fn 

1483def defaultload(*keys): 

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

1485 

1486 

1487@loader_option() 

1488def defer(loadopt, key): 

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

1490 e.g. not loaded until accessed. 

1491 

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

1493 both method-chained and standalone operation. 

1494 

1495 e.g.:: 

1496 

1497 from sqlalchemy.orm import defer 

1498 

1499 session.query(MyClass).options( 

1500 defer("attribute_one"), 

1501 defer("attribute_two")) 

1502 

1503 session.query(MyClass).options( 

1504 defer(MyClass.attribute_one), 

1505 defer(MyClass.attribute_two)) 

1506 

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

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

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

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

1511 

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

1513 

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

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

1516 each will operate on the same 

1517 parent entity:: 

1518 

1519 

1520 session.query(MyClass).options( 

1521 defaultload("someattr"). 

1522 defer("some_column"). 

1523 defer("some_other_column"). 

1524 defer("another_column") 

1525 ) 

1526 

1527 :param key: Attribute to be deferred. 

1528 

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

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

1531 by the method-chained style. 

1532 

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

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

1535 use method chaining in conjunction with defaultload() to 

1536 indicate a path. 

1537 

1538 

1539 .. seealso:: 

1540 

1541 :ref:`deferred` 

1542 

1543 :func:`_orm.undefer` 

1544 

1545 """ 

1546 return loadopt.set_column_strategy( 

1547 (key,), {"deferred": True, "instrument": True} 

1548 ) 

1549 

1550 

1551@defer._add_unbound_fn 

1552def defer(key, *addl_attrs): 

1553 if addl_attrs: 

1554 util.warn_deprecated( 

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

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

1557 "indicate a path." 

1558 ) 

1559 return _UnboundLoad._from_keys( 

1560 _UnboundLoad.defer, (key,) + addl_attrs, False, {} 

1561 ) 

1562 

1563 

1564@loader_option() 

1565def undefer(loadopt, key): 

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

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

1568 

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

1570 :func:`.deferred` attribute. 

1571 

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

1573 both method-chained and standalone operation. 

1574 

1575 Examples:: 

1576 

1577 # undefer two columns 

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

1579 

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

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

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

1583 

1584 # undefer a column on a related object 

1585 session.query(MyClass).options( 

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

1587 

1588 :param key: Attribute to be undeferred. 

1589 

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

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

1592 by the method-chained style. 

1593 

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

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

1596 use method chaining in conjunction with defaultload() to 

1597 indicate a path. 

1598 

1599 .. seealso:: 

1600 

1601 :ref:`deferred` 

1602 

1603 :func:`_orm.defer` 

1604 

1605 :func:`_orm.undefer_group` 

1606 

1607 """ 

1608 return loadopt.set_column_strategy( 

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

1610 ) 

1611 

1612 

1613@undefer._add_unbound_fn 

1614def undefer(key, *addl_attrs): 

1615 if addl_attrs: 

1616 util.warn_deprecated( 

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

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

1619 "indicate a path." 

1620 ) 

1621 return _UnboundLoad._from_keys( 

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

1623 ) 

1624 

1625 

1626@loader_option() 

1627def undefer_group(loadopt, name): 

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

1629 undeferred. 

1630 

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

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

1633 

1634 E.g:: 

1635 

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

1637 

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

1639 spelled out using relationship loader options, such as 

1640 :func:`_orm.defaultload`:: 

1641 

1642 session.query(MyClass).options( 

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

1644 

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

1646 particular entity load path. 

1647 

1648 .. seealso:: 

1649 

1650 :ref:`deferred` 

1651 

1652 :func:`_orm.defer` 

1653 

1654 :func:`_orm.undefer` 

1655 

1656 """ 

1657 return loadopt.set_column_strategy( 

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

1659 ) 

1660 

1661 

1662@undefer_group._add_unbound_fn 

1663def undefer_group(name): 

1664 return _UnboundLoad().undefer_group(name) 

1665 

1666 

1667@loader_option() 

1668def with_expression(loadopt, key, expression): 

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

1670 

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

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

1673 target of an ad-hoc SQL expression. 

1674 

1675 E.g.:: 

1676 

1677 

1678 sess.query(SomeClass).options( 

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

1680 ) 

1681 

1682 .. versionadded:: 1.2 

1683 

1684 :param key: Attribute to be undeferred. 

1685 

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

1687 

1688 .. seealso:: 

1689 

1690 :ref:`mapper_querytime_expression` 

1691 

1692 """ 

1693 

1694 expression = sql_expr._labeled(_orm_full_deannotate(expression)) 

1695 

1696 return loadopt.set_column_strategy( 

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

1698 ) 

1699 

1700 

1701@with_expression._add_unbound_fn 

1702def with_expression(key, expression): 

1703 return _UnboundLoad._from_keys( 

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

1705 ) 

1706 

1707 

1708@loader_option() 

1709def selectin_polymorphic(loadopt, classes): 

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

1711 specific to a subclass. 

1712 

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

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

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

1716 

1717 .. versionadded:: 1.2 

1718 

1719 .. seealso:: 

1720 

1721 :ref:`polymorphic_selectin` 

1722 

1723 """ 

1724 loadopt.set_class_strategy( 

1725 {"selectinload_polymorphic": True}, 

1726 opts={ 

1727 "entities": tuple( 

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

1729 ) 

1730 }, 

1731 ) 

1732 return loadopt 

1733 

1734 

1735@selectin_polymorphic._add_unbound_fn 

1736def selectin_polymorphic(base_cls, classes): 

1737 ul = _UnboundLoad() 

1738 ul.is_class_strategy = True 

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

1740 ul.selectin_polymorphic(classes) 

1741 return ul