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

995 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1# orm/relationships.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

8"""Heuristics related to join conditions as used in 

9:func:`_orm.relationship`. 

10 

11Provides the :class:`.JoinCondition` object, which encapsulates 

12SQL annotation and aliasing behavior focused on the `primaryjoin` 

13and `secondaryjoin` aspects of :func:`_orm.relationship`. 

14 

15""" 

16from __future__ import absolute_import 

17 

18import collections 

19import re 

20import weakref 

21 

22from . import attributes 

23from .base import _is_mapped_class 

24from .base import PASSIVE_MERGE 

25from .base import state_str 

26from .interfaces import MANYTOMANY 

27from .interfaces import MANYTOONE 

28from .interfaces import ONETOMANY 

29from .interfaces import PropComparator 

30from .interfaces import StrategizedProperty 

31from .util import _orm_annotate 

32from .util import _orm_deannotate 

33from .util import CascadeOptions 

34from .. import exc as sa_exc 

35from .. import log 

36from .. import schema 

37from .. import sql 

38from .. import util 

39from ..inspection import inspect 

40from ..sql import coercions 

41from ..sql import expression 

42from ..sql import operators 

43from ..sql import roles 

44from ..sql import visitors 

45from ..sql.util import _deep_deannotate 

46from ..sql.util import _shallow_annotate 

47from ..sql.util import adapt_criterion_to_null 

48from ..sql.util import ClauseAdapter 

49from ..sql.util import join_condition 

50from ..sql.util import selectables_overlap 

51from ..sql.util import visit_binary_product 

52 

53 

54def remote(expr): 

55 """Annotate a portion of a primaryjoin expression 

56 with a 'remote' annotation. 

57 

58 See the section :ref:`relationship_custom_foreign` for a 

59 description of use. 

60 

61 .. seealso:: 

62 

63 :ref:`relationship_custom_foreign` 

64 

65 :func:`.foreign` 

66 

67 """ 

68 return _annotate_columns( 

69 coercions.expect(roles.ColumnArgumentRole, expr), {"remote": True} 

70 ) 

71 

72 

73def foreign(expr): 

74 """Annotate a portion of a primaryjoin expression 

75 with a 'foreign' annotation. 

76 

77 See the section :ref:`relationship_custom_foreign` for a 

78 description of use. 

79 

80 .. seealso:: 

81 

82 :ref:`relationship_custom_foreign` 

83 

84 :func:`.remote` 

85 

86 """ 

87 

88 return _annotate_columns( 

89 coercions.expect(roles.ColumnArgumentRole, expr), {"foreign": True} 

90 ) 

91 

92 

93@log.class_logger 

94class RelationshipProperty(StrategizedProperty): 

95 """Describes an object property that holds a single item or list 

96 of items that correspond to a related database table. 

97 

98 Public constructor is the :func:`_orm.relationship` function. 

99 

100 .. seealso:: 

101 

102 :ref:`relationship_config_toplevel` 

103 

104 """ 

105 

106 strategy_wildcard_key = "relationship" 

107 inherit_cache = True 

108 

109 _links_to_entity = True 

110 

111 _persistence_only = dict( 

112 passive_deletes=False, 

113 passive_updates=True, 

114 enable_typechecks=True, 

115 active_history=False, 

116 cascade_backrefs=True, 

117 ) 

118 

119 _dependency_processor = None 

120 

121 def __init__( 

122 self, 

123 argument, 

124 secondary=None, 

125 primaryjoin=None, 

126 secondaryjoin=None, 

127 foreign_keys=None, 

128 uselist=None, 

129 order_by=False, 

130 backref=None, 

131 back_populates=None, 

132 overlaps=None, 

133 post_update=False, 

134 cascade=False, 

135 viewonly=False, 

136 lazy="select", 

137 collection_class=None, 

138 passive_deletes=_persistence_only["passive_deletes"], 

139 passive_updates=_persistence_only["passive_updates"], 

140 remote_side=None, 

141 enable_typechecks=_persistence_only["enable_typechecks"], 

142 join_depth=None, 

143 comparator_factory=None, 

144 single_parent=False, 

145 innerjoin=False, 

146 distinct_target_key=None, 

147 doc=None, 

148 active_history=_persistence_only["active_history"], 

149 cascade_backrefs=_persistence_only["cascade_backrefs"], 

150 load_on_pending=False, 

151 bake_queries=True, 

152 _local_remote_pairs=None, 

153 query_class=None, 

154 info=None, 

155 omit_join=None, 

156 sync_backref=None, 

157 _legacy_inactive_history_style=False, 

158 ): 

159 """Provide a relationship between two mapped classes. 

160 

161 This corresponds to a parent-child or associative table relationship. 

162 The constructed class is an instance of 

163 :class:`.RelationshipProperty`. 

164 

165 A typical :func:`_orm.relationship`, used in a classical mapping:: 

166 

167 mapper(Parent, properties={ 

168 'children': relationship(Child) 

169 }) 

170 

171 Some arguments accepted by :func:`_orm.relationship` 

172 optionally accept a 

173 callable function, which when called produces the desired value. 

174 The callable is invoked by the parent :class:`_orm.Mapper` at "mapper 

175 initialization" time, which happens only when mappers are first used, 

176 and is assumed to be after all mappings have been constructed. This 

177 can be used to resolve order-of-declaration and other dependency 

178 issues, such as if ``Child`` is declared below ``Parent`` in the same 

179 file:: 

180 

181 mapper(Parent, properties={ 

182 "children":relationship(lambda: Child, 

183 order_by=lambda: Child.id) 

184 }) 

185 

186 When using the :ref:`declarative_toplevel` extension, the Declarative 

187 initializer allows string arguments to be passed to 

188 :func:`_orm.relationship`. These string arguments are converted into 

189 callables that evaluate the string as Python code, using the 

190 Declarative class-registry as a namespace. This allows the lookup of 

191 related classes to be automatic via their string name, and removes the 

192 need for related classes to be imported into the local module space 

193 before the dependent classes have been declared. It is still required 

194 that the modules in which these related classes appear are imported 

195 anywhere in the application at some point before the related mappings 

196 are actually used, else a lookup error will be raised when the 

197 :func:`_orm.relationship` 

198 attempts to resolve the string reference to the 

199 related class. An example of a string- resolved class is as 

200 follows:: 

201 

202 from sqlalchemy.ext.declarative import declarative_base 

203 

204 Base = declarative_base() 

205 

206 class Parent(Base): 

207 __tablename__ = 'parent' 

208 id = Column(Integer, primary_key=True) 

209 children = relationship("Child", order_by="Child.id") 

210 

211 .. seealso:: 

212 

213 :ref:`relationship_config_toplevel` - Full introductory and 

214 reference documentation for :func:`_orm.relationship`. 

215 

216 :ref:`tutorial_orm_related_objects` - ORM tutorial introduction. 

217 

218 :param argument: 

219 A mapped class, or actual :class:`_orm.Mapper` instance, 

220 representing 

221 the target of the relationship. 

222 

223 :paramref:`_orm.relationship.argument` 

224 may also be passed as a callable 

225 function which is evaluated at mapper initialization time, and may 

226 be passed as a string name when using Declarative. 

227 

228 .. warning:: Prior to SQLAlchemy 1.3.16, this value is interpreted 

229 using Python's ``eval()`` function. 

230 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. 

231 See :ref:`declarative_relationship_eval` for details on 

232 declarative evaluation of :func:`_orm.relationship` arguments. 

233 

234 .. versionchanged 1.3.16:: 

235 

236 The string evaluation of the main "argument" no longer accepts an 

237 open ended Python expression, instead only accepting a string 

238 class name or dotted package-qualified name. 

239 

240 .. seealso:: 

241 

242 :ref:`declarative_configuring_relationships` - further detail 

243 on relationship configuration when using Declarative. 

244 

245 :param secondary: 

246 For a many-to-many relationship, specifies the intermediary 

247 table, and is typically an instance of :class:`_schema.Table`. 

248 In less common circumstances, the argument may also be specified 

249 as an :class:`_expression.Alias` construct, or even a 

250 :class:`_expression.Join` construct. 

251 

252 :paramref:`_orm.relationship.secondary` may 

253 also be passed as a callable function which is evaluated at 

254 mapper initialization time. When using Declarative, it may also 

255 be a string argument noting the name of a :class:`_schema.Table` 

256 that is 

257 present in the :class:`_schema.MetaData` 

258 collection associated with the 

259 parent-mapped :class:`_schema.Table`. 

260 

261 .. warning:: When passed as a Python-evaluable string, the 

262 argument is interpreted using Python's ``eval()`` function. 

263 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. 

264 See :ref:`declarative_relationship_eval` for details on 

265 declarative evaluation of :func:`_orm.relationship` arguments. 

266 

267 The :paramref:`_orm.relationship.secondary` keyword argument is 

268 typically applied in the case where the intermediary 

269 :class:`_schema.Table` 

270 is not otherwise expressed in any direct class mapping. If the 

271 "secondary" table is also explicitly mapped elsewhere (e.g. as in 

272 :ref:`association_pattern`), one should consider applying the 

273 :paramref:`_orm.relationship.viewonly` flag so that this 

274 :func:`_orm.relationship` 

275 is not used for persistence operations which 

276 may conflict with those of the association object pattern. 

277 

278 .. seealso:: 

279 

280 :ref:`relationships_many_to_many` - Reference example of "many 

281 to many". 

282 

283 :ref:`self_referential_many_to_many` - Specifics on using 

284 many-to-many in a self-referential case. 

285 

286 :ref:`declarative_many_to_many` - Additional options when using 

287 Declarative. 

288 

289 :ref:`association_pattern` - an alternative to 

290 :paramref:`_orm.relationship.secondary` 

291 when composing association 

292 table relationships, allowing additional attributes to be 

293 specified on the association table. 

294 

295 :ref:`composite_secondary_join` - a lesser-used pattern which 

296 in some cases can enable complex :func:`_orm.relationship` SQL 

297 conditions to be used. 

298 

299 .. versionadded:: 0.9.2 :paramref:`_orm.relationship.secondary` 

300 works 

301 more effectively when referring to a :class:`_expression.Join` 

302 instance. 

303 

304 :param active_history=False: 

305 When ``True``, indicates that the "previous" value for a 

306 many-to-one reference should be loaded when replaced, if 

307 not already loaded. Normally, history tracking logic for 

308 simple many-to-ones only needs to be aware of the "new" 

309 value in order to perform a flush. This flag is available 

310 for applications that make use of 

311 :func:`.attributes.get_history` which also need to know 

312 the "previous" value of the attribute. 

313 

314 :param backref: 

315 A reference to a string relationship name, or a :func:`_orm.backref` 

316 construct, which will be used to automatically generate a new 

317 :func:`_orm.relationship` on the related class, which then refers to 

318 this one using a bi-directional 

319 :paramref:`_orm.relationship.back_populates` configuration. 

320 

321 In modern Python, explicit use of :func:`_orm.relationship` with 

322 :paramref:`_orm.relationship.back_populates` should be preferred, as 

323 it is more robust in terms of mapper configuration as well as more 

324 conceptually straightforward. It also integrates with new :pep:`484` 

325 typing features introduced in SQLAlchemy 2.0 which is not possible 

326 with dynamically generated attributes. 

327 

328 .. seealso:: 

329 

330 :ref:`relationships_backref` - notes on using 

331 :paramref:`_orm.relationship.backref` 

332 

333 :ref:`tutorial_orm_related_objects` - in the 

334 :ref:`unified_tutorial`, presents an overview of bi-directional 

335 relationship configuration and behaviors using 

336 :paramref:`_orm.relationship.back_populates` 

337 

338 :func:`.backref` - allows control over :func:`_orm.relationship` 

339 configuration when using :paramref:`_orm.relationship.backref`. 

340 

341 

342 :param back_populates: 

343 Indicates the name of a :func:`_orm.relationship` on the related 

344 class that will be synchronized with this one. It is usually 

345 expected that the :func:`_orm.relationship` on the related class 

346 also refer to this one. This allows objects on both sides of 

347 each :func:`_orm.relationship` to synchronize in-Python state 

348 changes and also provides directives to the :term:`unit of work` 

349 flush process how changes along these relationships should 

350 be persisted. 

351 

352 .. seealso:: 

353 

354 :ref:`tutorial_orm_related_objects` - in the 

355 :ref:`unified_tutorial`, presents an overview of bi-directional 

356 relationship configuration and behaviors. 

357 

358 :ref:`relationship_patterns` - includes many examples of 

359 :paramref:`_orm.relationship.back_populates`. 

360 

361 :param overlaps: 

362 A string name or comma-delimited set of names of other relationships 

363 on either this mapper, a descendant mapper, or a target mapper with 

364 which this relationship may write to the same foreign keys upon 

365 persistence. The only effect this has is to eliminate the 

366 warning that this relationship will conflict with another upon 

367 persistence. This is used for such relationships that are truly 

368 capable of conflicting with each other on write, but the application 

369 will ensure that no such conflicts occur. 

370 

371 .. versionadded:: 1.4 

372 

373 .. seealso:: 

374 

375 :ref:`error_qzyx` - usage example 

376 

377 :param bake_queries=True: 

378 Legacy parameter, not used. 

379 

380 .. versionchanged:: 1.4.23 the "lambda caching" system is no longer 

381 used by loader strategies and the ``bake_queries`` parameter 

382 has no effect. 

383 

384 :param cascade: 

385 A comma-separated list of cascade rules which determines how 

386 Session operations should be "cascaded" from parent to child. 

387 This defaults to ``False``, which means the default cascade 

388 should be used - this default cascade is ``"save-update, merge"``. 

389 

390 The available cascades are ``save-update``, ``merge``, 

391 ``expunge``, ``delete``, ``delete-orphan``, and ``refresh-expire``. 

392 An additional option, ``all`` indicates shorthand for 

393 ``"save-update, merge, refresh-expire, 

394 expunge, delete"``, and is often used as in ``"all, delete-orphan"`` 

395 to indicate that related objects should follow along with the 

396 parent object in all cases, and be deleted when de-associated. 

397 

398 .. seealso:: 

399 

400 :ref:`unitofwork_cascades` - Full detail on each of the available 

401 cascade options. 

402 

403 :param cascade_backrefs=True: 

404 A boolean value indicating if the ``save-update`` cascade should 

405 operate along an assignment event intercepted by a backref. 

406 When set to ``False``, the attribute managed by this relationship 

407 will not cascade an incoming transient object into the session of a 

408 persistent parent, if the event is received via backref. 

409 

410 .. deprecated:: 1.4 The 

411 :paramref:`_orm.relationship.cascade_backrefs` 

412 flag will default to False in all cases in SQLAlchemy 2.0. 

413 

414 .. seealso:: 

415 

416 :ref:`backref_cascade` - Full discussion and examples on how 

417 the :paramref:`_orm.relationship.cascade_backrefs` option is used. 

418 

419 :param collection_class: 

420 A class or callable that returns a new list-holding object. will 

421 be used in place of a plain list for storing elements. 

422 

423 .. seealso:: 

424 

425 :ref:`custom_collections` - Introductory documentation and 

426 examples. 

427 

428 :param comparator_factory: 

429 A class which extends :class:`.RelationshipProperty.Comparator` 

430 which provides custom SQL clause generation for comparison 

431 operations. 

432 

433 .. seealso:: 

434 

435 :class:`.PropComparator` - some detail on redefining comparators 

436 at this level. 

437 

438 :ref:`custom_comparators` - Brief intro to this feature. 

439 

440 

441 :param distinct_target_key=None: 

442 Indicate if a "subquery" eager load should apply the DISTINCT 

443 keyword to the innermost SELECT statement. When left as ``None``, 

444 the DISTINCT keyword will be applied in those cases when the target 

445 columns do not comprise the full primary key of the target table. 

446 When set to ``True``, the DISTINCT keyword is applied to the 

447 innermost SELECT unconditionally. 

448 

449 It may be desirable to set this flag to False when the DISTINCT is 

450 reducing performance of the innermost subquery beyond that of what 

451 duplicate innermost rows may be causing. 

452 

453 .. versionchanged:: 0.9.0 - 

454 :paramref:`_orm.relationship.distinct_target_key` now defaults to 

455 ``None``, so that the feature enables itself automatically for 

456 those cases where the innermost query targets a non-unique 

457 key. 

458 

459 .. seealso:: 

460 

461 :ref:`loading_toplevel` - includes an introduction to subquery 

462 eager loading. 

463 

464 :param doc: 

465 Docstring which will be applied to the resulting descriptor. 

466 

467 :param foreign_keys: 

468 

469 A list of columns which are to be used as "foreign key" 

470 columns, or columns which refer to the value in a remote 

471 column, within the context of this :func:`_orm.relationship` 

472 object's :paramref:`_orm.relationship.primaryjoin` condition. 

473 That is, if the :paramref:`_orm.relationship.primaryjoin` 

474 condition of this :func:`_orm.relationship` is ``a.id == 

475 b.a_id``, and the values in ``b.a_id`` are required to be 

476 present in ``a.id``, then the "foreign key" column of this 

477 :func:`_orm.relationship` is ``b.a_id``. 

478 

479 In normal cases, the :paramref:`_orm.relationship.foreign_keys` 

480 parameter is **not required.** :func:`_orm.relationship` will 

481 automatically determine which columns in the 

482 :paramref:`_orm.relationship.primaryjoin` condition are to be 

483 considered "foreign key" columns based on those 

484 :class:`_schema.Column` objects that specify 

485 :class:`_schema.ForeignKey`, 

486 or are otherwise listed as referencing columns in a 

487 :class:`_schema.ForeignKeyConstraint` construct. 

488 :paramref:`_orm.relationship.foreign_keys` is only needed when: 

489 

490 1. There is more than one way to construct a join from the local 

491 table to the remote table, as there are multiple foreign key 

492 references present. Setting ``foreign_keys`` will limit the 

493 :func:`_orm.relationship` 

494 to consider just those columns specified 

495 here as "foreign". 

496 

497 2. The :class:`_schema.Table` being mapped does not actually have 

498 :class:`_schema.ForeignKey` or 

499 :class:`_schema.ForeignKeyConstraint` 

500 constructs present, often because the table 

501 was reflected from a database that does not support foreign key 

502 reflection (MySQL MyISAM). 

503 

504 3. The :paramref:`_orm.relationship.primaryjoin` 

505 argument is used to 

506 construct a non-standard join condition, which makes use of 

507 columns or expressions that do not normally refer to their 

508 "parent" column, such as a join condition expressed by a 

509 complex comparison using a SQL function. 

510 

511 The :func:`_orm.relationship` construct will raise informative 

512 error messages that suggest the use of the 

513 :paramref:`_orm.relationship.foreign_keys` parameter when 

514 presented with an ambiguous condition. In typical cases, 

515 if :func:`_orm.relationship` doesn't raise any exceptions, the 

516 :paramref:`_orm.relationship.foreign_keys` parameter is usually 

517 not needed. 

518 

519 :paramref:`_orm.relationship.foreign_keys` may also be passed as a 

520 callable function which is evaluated at mapper initialization time, 

521 and may be passed as a Python-evaluable string when using 

522 Declarative. 

523 

524 .. warning:: When passed as a Python-evaluable string, the 

525 argument is interpreted using Python's ``eval()`` function. 

526 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. 

527 See :ref:`declarative_relationship_eval` for details on 

528 declarative evaluation of :func:`_orm.relationship` arguments. 

529 

530 .. seealso:: 

531 

532 :ref:`relationship_foreign_keys` 

533 

534 :ref:`relationship_custom_foreign` 

535 

536 :func:`.foreign` - allows direct annotation of the "foreign" 

537 columns within a :paramref:`_orm.relationship.primaryjoin` 

538 condition. 

539 

540 :param info: Optional data dictionary which will be populated into the 

541 :attr:`.MapperProperty.info` attribute of this object. 

542 

543 :param innerjoin=False: 

544 When ``True``, joined eager loads will use an inner join to join 

545 against related tables instead of an outer join. The purpose 

546 of this option is generally one of performance, as inner joins 

547 generally perform better than outer joins. 

548 

549 This flag can be set to ``True`` when the relationship references an 

550 object via many-to-one using local foreign keys that are not 

551 nullable, or when the reference is one-to-one or a collection that 

552 is guaranteed to have one or at least one entry. 

553 

554 The option supports the same "nested" and "unnested" options as 

555 that of :paramref:`_orm.joinedload.innerjoin`. See that flag 

556 for details on nested / unnested behaviors. 

557 

558 .. seealso:: 

559 

560 :paramref:`_orm.joinedload.innerjoin` - the option as specified by 

561 loader option, including detail on nesting behavior. 

562 

563 :ref:`what_kind_of_loading` - Discussion of some details of 

564 various loader options. 

565 

566 

567 :param join_depth: 

568 When non-``None``, an integer value indicating how many levels 

569 deep "eager" loaders should join on a self-referring or cyclical 

570 relationship. The number counts how many times the same Mapper 

571 shall be present in the loading condition along a particular join 

572 branch. When left at its default of ``None``, eager loaders 

573 will stop chaining when they encounter a the same target mapper 

574 which is already higher up in the chain. This option applies 

575 both to joined- and subquery- eager loaders. 

576 

577 .. seealso:: 

578 

579 :ref:`self_referential_eager_loading` - Introductory documentation 

580 and examples. 

581 

582 :param lazy='select': specifies 

583 How the related items should be loaded. Default value is 

584 ``select``. Values include: 

585 

586 * ``select`` - items should be loaded lazily when the property is 

587 first accessed, using a separate SELECT statement, or identity map 

588 fetch for simple many-to-one references. 

589 

590 * ``immediate`` - items should be loaded as the parents are loaded, 

591 using a separate SELECT statement, or identity map fetch for 

592 simple many-to-one references. 

593 

594 * ``joined`` - items should be loaded "eagerly" in the same query as 

595 that of the parent, using a JOIN or LEFT OUTER JOIN. Whether 

596 the join is "outer" or not is determined by the 

597 :paramref:`_orm.relationship.innerjoin` parameter. 

598 

599 * ``subquery`` - items should be loaded "eagerly" as the parents are 

600 loaded, using one additional SQL statement, which issues a JOIN to 

601 a subquery of the original statement, for each collection 

602 requested. 

603 

604 * ``selectin`` - items should be loaded "eagerly" as the parents 

605 are loaded, using one or more additional SQL statements, which 

606 issues a JOIN to the immediate parent object, specifying primary 

607 key identifiers using an IN clause. 

608 

609 .. versionadded:: 1.2 

610 

611 * ``noload`` - no loading should occur at any time. This is to 

612 support "write-only" attributes, or attributes which are 

613 populated in some manner specific to the application. 

614 

615 * ``raise`` - lazy loading is disallowed; accessing 

616 the attribute, if its value were not already loaded via eager 

617 loading, will raise an :exc:`~sqlalchemy.exc.InvalidRequestError`. 

618 This strategy can be used when objects are to be detached from 

619 their attached :class:`.Session` after they are loaded. 

620 

621 .. versionadded:: 1.1 

622 

623 * ``raise_on_sql`` - lazy loading that emits SQL is disallowed; 

624 accessing the attribute, if its value were not already loaded via 

625 eager loading, will raise an 

626 :exc:`~sqlalchemy.exc.InvalidRequestError`, **if the lazy load 

627 needs to emit SQL**. If the lazy load can pull the related value 

628 from the identity map or determine that it should be None, the 

629 value is loaded. This strategy can be used when objects will 

630 remain associated with the attached :class:`.Session`, however 

631 additional SELECT statements should be blocked. 

632 

633 .. versionadded:: 1.1 

634 

635 * ``dynamic`` - the attribute will return a pre-configured 

636 :class:`_query.Query` object for all read 

637 operations, onto which further filtering operations can be 

638 applied before iterating the results. See 

639 the section :ref:`dynamic_relationship` for more details. 

640 

641 * True - a synonym for 'select' 

642 

643 * False - a synonym for 'joined' 

644 

645 * None - a synonym for 'noload' 

646 

647 .. seealso:: 

648 

649 :doc:`/orm/loading_relationships` - Full documentation on 

650 relationship loader configuration. 

651 

652 :ref:`dynamic_relationship` - detail on the ``dynamic`` option. 

653 

654 :ref:`collections_noload_raiseload` - notes on "noload" and "raise" 

655 

656 :param load_on_pending=False: 

657 Indicates loading behavior for transient or pending parent objects. 

658 

659 When set to ``True``, causes the lazy-loader to 

660 issue a query for a parent object that is not persistent, meaning it 

661 has never been flushed. This may take effect for a pending object 

662 when autoflush is disabled, or for a transient object that has been 

663 "attached" to a :class:`.Session` but is not part of its pending 

664 collection. 

665 

666 The :paramref:`_orm.relationship.load_on_pending` 

667 flag does not improve 

668 behavior when the ORM is used normally - object references should be 

669 constructed at the object level, not at the foreign key level, so 

670 that they are present in an ordinary way before a flush proceeds. 

671 This flag is not not intended for general use. 

672 

673 .. seealso:: 

674 

675 :meth:`.Session.enable_relationship_loading` - this method 

676 establishes "load on pending" behavior for the whole object, and 

677 also allows loading on objects that remain transient or 

678 detached. 

679 

680 :param order_by: 

681 Indicates the ordering that should be applied when loading these 

682 items. :paramref:`_orm.relationship.order_by` 

683 is expected to refer to 

684 one of the :class:`_schema.Column` 

685 objects to which the target class is 

686 mapped, or the attribute itself bound to the target class which 

687 refers to the column. 

688 

689 :paramref:`_orm.relationship.order_by` 

690 may also be passed as a callable 

691 function which is evaluated at mapper initialization time, and may 

692 be passed as a Python-evaluable string when using Declarative. 

693 

694 .. warning:: When passed as a Python-evaluable string, the 

695 argument is interpreted using Python's ``eval()`` function. 

696 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. 

697 See :ref:`declarative_relationship_eval` for details on 

698 declarative evaluation of :func:`_orm.relationship` arguments. 

699 

700 :param passive_deletes=False: 

701 Indicates loading behavior during delete operations. 

702 

703 A value of True indicates that unloaded child items should not 

704 be loaded during a delete operation on the parent. Normally, 

705 when a parent item is deleted, all child items are loaded so 

706 that they can either be marked as deleted, or have their 

707 foreign key to the parent set to NULL. Marking this flag as 

708 True usually implies an ON DELETE <CASCADE|SET NULL> rule is in 

709 place which will handle updating/deleting child rows on the 

710 database side. 

711 

712 Additionally, setting the flag to the string value 'all' will 

713 disable the "nulling out" of the child foreign keys, when the parent 

714 object is deleted and there is no delete or delete-orphan cascade 

715 enabled. This is typically used when a triggering or error raise 

716 scenario is in place on the database side. Note that the foreign 

717 key attributes on in-session child objects will not be changed after 

718 a flush occurs so this is a very special use-case setting. 

719 Additionally, the "nulling out" will still occur if the child 

720 object is de-associated with the parent. 

721 

722 .. seealso:: 

723 

724 :ref:`passive_deletes` - Introductory documentation 

725 and examples. 

726 

727 :param passive_updates=True: 

728 Indicates the persistence behavior to take when a referenced 

729 primary key value changes in place, indicating that the referencing 

730 foreign key columns will also need their value changed. 

731 

732 When True, it is assumed that ``ON UPDATE CASCADE`` is configured on 

733 the foreign key in the database, and that the database will 

734 handle propagation of an UPDATE from a source column to 

735 dependent rows. When False, the SQLAlchemy 

736 :func:`_orm.relationship` 

737 construct will attempt to emit its own UPDATE statements to 

738 modify related targets. However note that SQLAlchemy **cannot** 

739 emit an UPDATE for more than one level of cascade. Also, 

740 setting this flag to False is not compatible in the case where 

741 the database is in fact enforcing referential integrity, unless 

742 those constraints are explicitly "deferred", if the target backend 

743 supports it. 

744 

745 It is highly advised that an application which is employing 

746 mutable primary keys keeps ``passive_updates`` set to True, 

747 and instead uses the referential integrity features of the database 

748 itself in order to handle the change efficiently and fully. 

749 

750 .. seealso:: 

751 

752 :ref:`passive_updates` - Introductory documentation and 

753 examples. 

754 

755 :paramref:`.mapper.passive_updates` - a similar flag which 

756 takes effect for joined-table inheritance mappings. 

757 

758 :param post_update: 

759 This indicates that the relationship should be handled by a 

760 second UPDATE statement after an INSERT or before a 

761 DELETE. This flag is used to handle saving bi-directional 

762 dependencies between two individual rows (i.e. each row 

763 references the other), where it would otherwise be impossible to 

764 INSERT or DELETE both rows fully since one row exists before the 

765 other. Use this flag when a particular mapping arrangement will 

766 incur two rows that are dependent on each other, such as a table 

767 that has a one-to-many relationship to a set of child rows, and 

768 also has a column that references a single child row within that 

769 list (i.e. both tables contain a foreign key to each other). If 

770 a flush operation returns an error that a "cyclical 

771 dependency" was detected, this is a cue that you might want to 

772 use :paramref:`_orm.relationship.post_update` to "break" the cycle. 

773 

774 .. seealso:: 

775 

776 :ref:`post_update` - Introductory documentation and examples. 

777 

778 :param primaryjoin: 

779 A SQL expression that will be used as the primary 

780 join of the child object against the parent object, or in a 

781 many-to-many relationship the join of the parent object to the 

782 association table. By default, this value is computed based on the 

783 foreign key relationships of the parent and child tables (or 

784 association table). 

785 

786 :paramref:`_orm.relationship.primaryjoin` may also be passed as a 

787 callable function which is evaluated at mapper initialization time, 

788 and may be passed as a Python-evaluable string when using 

789 Declarative. 

790 

791 .. warning:: When passed as a Python-evaluable string, the 

792 argument is interpreted using Python's ``eval()`` function. 

793 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. 

794 See :ref:`declarative_relationship_eval` for details on 

795 declarative evaluation of :func:`_orm.relationship` arguments. 

796 

797 .. seealso:: 

798 

799 :ref:`relationship_primaryjoin` 

800 

801 :param remote_side: 

802 Used for self-referential relationships, indicates the column or 

803 list of columns that form the "remote side" of the relationship. 

804 

805 :paramref:`_orm.relationship.remote_side` may also be passed as a 

806 callable function which is evaluated at mapper initialization time, 

807 and may be passed as a Python-evaluable string when using 

808 Declarative. 

809 

810 .. warning:: When passed as a Python-evaluable string, the 

811 argument is interpreted using Python's ``eval()`` function. 

812 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. 

813 See :ref:`declarative_relationship_eval` for details on 

814 declarative evaluation of :func:`_orm.relationship` arguments. 

815 

816 .. seealso:: 

817 

818 :ref:`self_referential` - in-depth explanation of how 

819 :paramref:`_orm.relationship.remote_side` 

820 is used to configure self-referential relationships. 

821 

822 :func:`.remote` - an annotation function that accomplishes the 

823 same purpose as :paramref:`_orm.relationship.remote_side`, 

824 typically 

825 when a custom :paramref:`_orm.relationship.primaryjoin` condition 

826 is used. 

827 

828 :param query_class: 

829 A :class:`_query.Query` 

830 subclass that will be used internally by the 

831 ``AppenderQuery`` returned by a "dynamic" relationship, that 

832 is, a relationship that specifies ``lazy="dynamic"`` or was 

833 otherwise constructed using the :func:`_orm.dynamic_loader` 

834 function. 

835 

836 .. seealso:: 

837 

838 :ref:`dynamic_relationship` - Introduction to "dynamic" 

839 relationship loaders. 

840 

841 :param secondaryjoin: 

842 A SQL expression that will be used as the join of 

843 an association table to the child object. By default, this value is 

844 computed based on the foreign key relationships of the association 

845 and child tables. 

846 

847 :paramref:`_orm.relationship.secondaryjoin` may also be passed as a 

848 callable function which is evaluated at mapper initialization time, 

849 and may be passed as a Python-evaluable string when using 

850 Declarative. 

851 

852 .. warning:: When passed as a Python-evaluable string, the 

853 argument is interpreted using Python's ``eval()`` function. 

854 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**. 

855 See :ref:`declarative_relationship_eval` for details on 

856 declarative evaluation of :func:`_orm.relationship` arguments. 

857 

858 .. seealso:: 

859 

860 :ref:`relationship_primaryjoin` 

861 

862 :param single_parent: 

863 When True, installs a validator which will prevent objects 

864 from being associated with more than one parent at a time. 

865 This is used for many-to-one or many-to-many relationships that 

866 should be treated either as one-to-one or one-to-many. Its usage 

867 is optional, except for :func:`_orm.relationship` constructs which 

868 are many-to-one or many-to-many and also 

869 specify the ``delete-orphan`` cascade option. The 

870 :func:`_orm.relationship` construct itself will raise an error 

871 instructing when this option is required. 

872 

873 .. seealso:: 

874 

875 :ref:`unitofwork_cascades` - includes detail on when the 

876 :paramref:`_orm.relationship.single_parent` 

877 flag may be appropriate. 

878 

879 :param uselist: 

880 A boolean that indicates if this property should be loaded as a 

881 list or a scalar. In most cases, this value is determined 

882 automatically by :func:`_orm.relationship` at mapper configuration 

883 time, based on the type and direction 

884 of the relationship - one to many forms a list, many to one 

885 forms a scalar, many to many is a list. If a scalar is desired 

886 where normally a list would be present, such as a bi-directional 

887 one-to-one relationship, set :paramref:`_orm.relationship.uselist` 

888 to 

889 False. 

890 

891 The :paramref:`_orm.relationship.uselist` 

892 flag is also available on an 

893 existing :func:`_orm.relationship` 

894 construct as a read-only attribute, 

895 which can be used to determine if this :func:`_orm.relationship` 

896 deals 

897 with collections or scalar attributes:: 

898 

899 >>> User.addresses.property.uselist 

900 True 

901 

902 .. seealso:: 

903 

904 :ref:`relationships_one_to_one` - Introduction to the "one to 

905 one" relationship pattern, which is typically when the 

906 :paramref:`_orm.relationship.uselist` flag is needed. 

907 

908 :param viewonly=False: 

909 When set to ``True``, the relationship is used only for loading 

910 objects, and not for any persistence operation. A 

911 :func:`_orm.relationship` which specifies 

912 :paramref:`_orm.relationship.viewonly` can work 

913 with a wider range of SQL operations within the 

914 :paramref:`_orm.relationship.primaryjoin` condition, including 

915 operations that feature the use of a variety of comparison operators 

916 as well as SQL functions such as :func:`_expression.cast`. The 

917 :paramref:`_orm.relationship.viewonly` 

918 flag is also of general use when defining any kind of 

919 :func:`_orm.relationship` that doesn't represent 

920 the full set of related objects, to prevent modifications of the 

921 collection from resulting in persistence operations. 

922 

923 When using the :paramref:`_orm.relationship.viewonly` flag in 

924 conjunction with backrefs, the originating relationship for a 

925 particular state change will not produce state changes within the 

926 viewonly relationship. This is the behavior implied by 

927 :paramref:`_orm.relationship.sync_backref` being set to False. 

928 

929 .. versionchanged:: 1.3.17 - the 

930 :paramref:`_orm.relationship.sync_backref` flag is set to False 

931 when using viewonly in conjunction with backrefs. 

932 

933 .. seealso:: 

934 

935 :paramref:`_orm.relationship.sync_backref` 

936 

937 :param sync_backref: 

938 A boolean that enables the events used to synchronize the in-Python 

939 attributes when this relationship is target of either 

940 :paramref:`_orm.relationship.backref` or 

941 :paramref:`_orm.relationship.back_populates`. 

942 

943 Defaults to ``None``, which indicates that an automatic value should 

944 be selected based on the value of the 

945 :paramref:`_orm.relationship.viewonly` flag. When left at its 

946 default, changes in state will be back-populated only if neither 

947 sides of a relationship is viewonly. 

948 

949 .. versionadded:: 1.3.17 

950 

951 .. versionchanged:: 1.4 - A relationship that specifies 

952 :paramref:`_orm.relationship.viewonly` automatically implies 

953 that :paramref:`_orm.relationship.sync_backref` is ``False``. 

954 

955 .. seealso:: 

956 

957 :paramref:`_orm.relationship.viewonly` 

958 

959 :param omit_join: 

960 Allows manual control over the "selectin" automatic join 

961 optimization. Set to ``False`` to disable the "omit join" feature 

962 added in SQLAlchemy 1.3; or leave as ``None`` to leave automatic 

963 optimization in place. 

964 

965 .. note:: This flag may only be set to ``False``. It is not 

966 necessary to set it to ``True`` as the "omit_join" optimization is 

967 automatically detected; if it is not detected, then the 

968 optimization is not supported. 

969 

970 .. versionchanged:: 1.3.11 setting ``omit_join`` to True will now 

971 emit a warning as this was not the intended use of this flag. 

972 

973 .. versionadded:: 1.3 

974 

975 

976 """ 

977 super(RelationshipProperty, self).__init__() 

978 

979 self.uselist = uselist 

980 self.argument = argument 

981 self.secondary = secondary 

982 self.primaryjoin = primaryjoin 

983 self.secondaryjoin = secondaryjoin 

984 self.post_update = post_update 

985 self.direction = None 

986 self.viewonly = viewonly 

987 if viewonly: 

988 self._warn_for_persistence_only_flags( 

989 passive_deletes=passive_deletes, 

990 passive_updates=passive_updates, 

991 enable_typechecks=enable_typechecks, 

992 active_history=active_history, 

993 cascade_backrefs=cascade_backrefs, 

994 ) 

995 if viewonly and sync_backref: 

996 raise sa_exc.ArgumentError( 

997 "sync_backref and viewonly cannot both be True" 

998 ) 

999 self.sync_backref = sync_backref 

1000 self.lazy = lazy 

1001 self.single_parent = single_parent 

1002 self._user_defined_foreign_keys = foreign_keys 

1003 self.collection_class = collection_class 

1004 self.passive_deletes = passive_deletes 

1005 self.cascade_backrefs = cascade_backrefs 

1006 self.passive_updates = passive_updates 

1007 self.remote_side = remote_side 

1008 self.enable_typechecks = enable_typechecks 

1009 self.query_class = query_class 

1010 self.innerjoin = innerjoin 

1011 self.distinct_target_key = distinct_target_key 

1012 self.doc = doc 

1013 self.active_history = active_history 

1014 self._legacy_inactive_history_style = _legacy_inactive_history_style 

1015 

1016 self.join_depth = join_depth 

1017 if omit_join: 

1018 util.warn( 

1019 "setting omit_join to True is not supported; selectin " 

1020 "loading of this relationship may not work correctly if this " 

1021 "flag is set explicitly. omit_join optimization is " 

1022 "automatically detected for conditions under which it is " 

1023 "supported." 

1024 ) 

1025 

1026 self.omit_join = omit_join 

1027 self.local_remote_pairs = _local_remote_pairs 

1028 self.bake_queries = bake_queries 

1029 self.load_on_pending = load_on_pending 

1030 self.comparator_factory = ( 

1031 comparator_factory or RelationshipProperty.Comparator 

1032 ) 

1033 self.comparator = self.comparator_factory(self, None) 

1034 util.set_creation_order(self) 

1035 

1036 if info is not None: 

1037 self.info = info 

1038 

1039 self.strategy_key = (("lazy", self.lazy),) 

1040 

1041 self._reverse_property = set() 

1042 if overlaps: 

1043 self._overlaps = set(re.split(r"\s*,\s*", overlaps)) 

1044 else: 

1045 self._overlaps = () 

1046 

1047 if cascade is not False: 

1048 self.cascade = cascade 

1049 elif self.viewonly: 

1050 self.cascade = "merge" 

1051 else: 

1052 self.cascade = "save-update, merge" 

1053 

1054 self.order_by = order_by 

1055 

1056 self.back_populates = back_populates 

1057 

1058 if self.back_populates: 

1059 if backref: 

1060 raise sa_exc.ArgumentError( 

1061 "backref and back_populates keyword arguments " 

1062 "are mutually exclusive" 

1063 ) 

1064 self.backref = None 

1065 else: 

1066 self.backref = backref 

1067 

1068 def _warn_for_persistence_only_flags(self, **kw): 

1069 for k, v in kw.items(): 

1070 if v != self._persistence_only[k]: 

1071 # we are warning here rather than warn deprecated as this is a 

1072 # configuration mistake, and Python shows regular warnings more 

1073 # aggressively than deprecation warnings by default. Unlike the 

1074 # case of setting viewonly with cascade, the settings being 

1075 # warned about here are not actively doing the wrong thing 

1076 # against viewonly=True, so it is not as urgent to have these 

1077 # raise an error. 

1078 util.warn( 

1079 "Setting %s on relationship() while also " 

1080 "setting viewonly=True does not make sense, as a " 

1081 "viewonly=True relationship does not perform persistence " 

1082 "operations. This configuration may raise an error " 

1083 "in a future release." % (k,) 

1084 ) 

1085 

1086 def instrument_class(self, mapper): 

1087 attributes.register_descriptor( 

1088 mapper.class_, 

1089 self.key, 

1090 comparator=self.comparator_factory(self, mapper), 

1091 parententity=mapper, 

1092 doc=self.doc, 

1093 ) 

1094 

1095 class Comparator(PropComparator): 

1096 """Produce boolean, comparison, and other operators for 

1097 :class:`.RelationshipProperty` attributes. 

1098 

1099 See the documentation for :class:`.PropComparator` for a brief 

1100 overview of ORM level operator definition. 

1101 

1102 .. seealso:: 

1103 

1104 :class:`.PropComparator` 

1105 

1106 :class:`.ColumnProperty.Comparator` 

1107 

1108 :class:`.ColumnOperators` 

1109 

1110 :ref:`types_operators` 

1111 

1112 :attr:`.TypeEngine.comparator_factory` 

1113 

1114 """ 

1115 

1116 _of_type = None 

1117 _extra_criteria = () 

1118 

1119 def __init__( 

1120 self, 

1121 prop, 

1122 parentmapper, 

1123 adapt_to_entity=None, 

1124 of_type=None, 

1125 extra_criteria=(), 

1126 ): 

1127 """Construction of :class:`.RelationshipProperty.Comparator` 

1128 is internal to the ORM's attribute mechanics. 

1129 

1130 """ 

1131 self.prop = prop 

1132 self._parententity = parentmapper 

1133 self._adapt_to_entity = adapt_to_entity 

1134 if of_type: 

1135 self._of_type = of_type 

1136 self._extra_criteria = extra_criteria 

1137 

1138 def adapt_to_entity(self, adapt_to_entity): 

1139 return self.__class__( 

1140 self.property, 

1141 self._parententity, 

1142 adapt_to_entity=adapt_to_entity, 

1143 of_type=self._of_type, 

1144 ) 

1145 

1146 @util.memoized_property 

1147 def entity(self): 

1148 """The target entity referred to by this 

1149 :class:`.RelationshipProperty.Comparator`. 

1150 

1151 This is either a :class:`_orm.Mapper` or :class:`.AliasedInsp` 

1152 object. 

1153 

1154 This is the "target" or "remote" side of the 

1155 :func:`_orm.relationship`. 

1156 

1157 """ 

1158 # this is a relatively recent change made for 

1159 # 1.4.27 as part of #7244. 

1160 # TODO: shouldn't _of_type be inspected up front when received? 

1161 if self._of_type is not None: 

1162 return inspect(self._of_type) 

1163 else: 

1164 return self.property.entity 

1165 

1166 @util.memoized_property 

1167 def mapper(self): 

1168 """The target :class:`_orm.Mapper` referred to by this 

1169 :class:`.RelationshipProperty.Comparator`. 

1170 

1171 This is the "target" or "remote" side of the 

1172 :func:`_orm.relationship`. 

1173 

1174 """ 

1175 return self.property.mapper 

1176 

1177 @util.memoized_property 

1178 def _parententity(self): 

1179 return self.property.parent 

1180 

1181 def _source_selectable(self): 

1182 if self._adapt_to_entity: 

1183 return self._adapt_to_entity.selectable 

1184 else: 

1185 return self.property.parent._with_polymorphic_selectable 

1186 

1187 def __clause_element__(self): 

1188 adapt_from = self._source_selectable() 

1189 if self._of_type: 

1190 of_type_entity = inspect(self._of_type) 

1191 else: 

1192 of_type_entity = None 

1193 

1194 ( 

1195 pj, 

1196 sj, 

1197 source, 

1198 dest, 

1199 secondary, 

1200 target_adapter, 

1201 ) = self.property._create_joins( 

1202 source_selectable=adapt_from, 

1203 source_polymorphic=True, 

1204 of_type_entity=of_type_entity, 

1205 alias_secondary=True, 

1206 extra_criteria=self._extra_criteria, 

1207 ) 

1208 if sj is not None: 

1209 return pj & sj 

1210 else: 

1211 return pj 

1212 

1213 def of_type(self, cls): 

1214 r"""Redefine this object in terms of a polymorphic subclass. 

1215 

1216 See :meth:`.PropComparator.of_type` for an example. 

1217 

1218 

1219 """ 

1220 return RelationshipProperty.Comparator( 

1221 self.property, 

1222 self._parententity, 

1223 adapt_to_entity=self._adapt_to_entity, 

1224 of_type=cls, 

1225 extra_criteria=self._extra_criteria, 

1226 ) 

1227 

1228 def and_(self, *other): 

1229 """Add AND criteria. 

1230 

1231 See :meth:`.PropComparator.and_` for an example. 

1232 

1233 .. versionadded:: 1.4 

1234 

1235 """ 

1236 return RelationshipProperty.Comparator( 

1237 self.property, 

1238 self._parententity, 

1239 adapt_to_entity=self._adapt_to_entity, 

1240 of_type=self._of_type, 

1241 extra_criteria=self._extra_criteria + other, 

1242 ) 

1243 

1244 def in_(self, other): 

1245 """Produce an IN clause - this is not implemented 

1246 for :func:`_orm.relationship`-based attributes at this time. 

1247 

1248 """ 

1249 raise NotImplementedError( 

1250 "in_() not yet supported for " 

1251 "relationships. For a simple " 

1252 "many-to-one, use in_() against " 

1253 "the set of foreign key values." 

1254 ) 

1255 

1256 __hash__ = None 

1257 

1258 def __eq__(self, other): 

1259 """Implement the ``==`` operator. 

1260 

1261 In a many-to-one context, such as:: 

1262 

1263 MyClass.some_prop == <some object> 

1264 

1265 this will typically produce a 

1266 clause such as:: 

1267 

1268 mytable.related_id == <some id> 

1269 

1270 Where ``<some id>`` is the primary key of the given 

1271 object. 

1272 

1273 The ``==`` operator provides partial functionality for non- 

1274 many-to-one comparisons: 

1275 

1276 * Comparisons against collections are not supported. 

1277 Use :meth:`~.RelationshipProperty.Comparator.contains`. 

1278 * Compared to a scalar one-to-many, will produce a 

1279 clause that compares the target columns in the parent to 

1280 the given target. 

1281 * Compared to a scalar many-to-many, an alias 

1282 of the association table will be rendered as 

1283 well, forming a natural join that is part of the 

1284 main body of the query. This will not work for 

1285 queries that go beyond simple AND conjunctions of 

1286 comparisons, such as those which use OR. Use 

1287 explicit joins, outerjoins, or 

1288 :meth:`~.RelationshipProperty.Comparator.has` for 

1289 more comprehensive non-many-to-one scalar 

1290 membership tests. 

1291 * Comparisons against ``None`` given in a one-to-many 

1292 or many-to-many context produce a NOT EXISTS clause. 

1293 

1294 """ 

1295 if isinstance(other, (util.NoneType, expression.Null)): 

1296 if self.property.direction in [ONETOMANY, MANYTOMANY]: 

1297 return ~self._criterion_exists() 

1298 else: 

1299 return _orm_annotate( 

1300 self.property._optimized_compare( 

1301 None, adapt_source=self.adapter 

1302 ) 

1303 ) 

1304 elif self.property.uselist: 

1305 raise sa_exc.InvalidRequestError( 

1306 "Can't compare a collection to an object or collection; " 

1307 "use contains() to test for membership." 

1308 ) 

1309 else: 

1310 return _orm_annotate( 

1311 self.property._optimized_compare( 

1312 other, adapt_source=self.adapter 

1313 ) 

1314 ) 

1315 

1316 def _criterion_exists(self, criterion=None, **kwargs): 

1317 if getattr(self, "_of_type", None): 

1318 info = inspect(self._of_type) 

1319 target_mapper, to_selectable, is_aliased_class = ( 

1320 info.mapper, 

1321 info.selectable, 

1322 info.is_aliased_class, 

1323 ) 

1324 if self.property._is_self_referential and not is_aliased_class: 

1325 to_selectable = to_selectable._anonymous_fromclause() 

1326 

1327 single_crit = target_mapper._single_table_criterion 

1328 if single_crit is not None: 

1329 if criterion is not None: 

1330 criterion = single_crit & criterion 

1331 else: 

1332 criterion = single_crit 

1333 else: 

1334 is_aliased_class = False 

1335 to_selectable = None 

1336 

1337 if self.adapter: 

1338 source_selectable = self._source_selectable() 

1339 else: 

1340 source_selectable = None 

1341 

1342 ( 

1343 pj, 

1344 sj, 

1345 source, 

1346 dest, 

1347 secondary, 

1348 target_adapter, 

1349 ) = self.property._create_joins( 

1350 dest_selectable=to_selectable, 

1351 source_selectable=source_selectable, 

1352 ) 

1353 

1354 for k in kwargs: 

1355 crit = getattr(self.property.mapper.class_, k) == kwargs[k] 

1356 if criterion is None: 

1357 criterion = crit 

1358 else: 

1359 criterion = criterion & crit 

1360 

1361 # annotate the *local* side of the join condition, in the case 

1362 # of pj + sj this is the full primaryjoin, in the case of just 

1363 # pj its the local side of the primaryjoin. 

1364 if sj is not None: 

1365 j = _orm_annotate(pj) & sj 

1366 else: 

1367 j = _orm_annotate(pj, exclude=self.property.remote_side) 

1368 

1369 if ( 

1370 criterion is not None 

1371 and target_adapter 

1372 and not is_aliased_class 

1373 ): 

1374 # limit this adapter to annotated only? 

1375 criterion = target_adapter.traverse(criterion) 

1376 

1377 # only have the "joined left side" of what we 

1378 # return be subject to Query adaption. The right 

1379 # side of it is used for an exists() subquery and 

1380 # should not correlate or otherwise reach out 

1381 # to anything in the enclosing query. 

1382 if criterion is not None: 

1383 criterion = criterion._annotate( 

1384 {"no_replacement_traverse": True} 

1385 ) 

1386 

1387 crit = j & sql.True_._ifnone(criterion) 

1388 

1389 if secondary is not None: 

1390 ex = ( 

1391 sql.exists(1) 

1392 .where(crit) 

1393 .select_from(dest, secondary) 

1394 .correlate_except(dest, secondary) 

1395 ) 

1396 else: 

1397 ex = ( 

1398 sql.exists(1) 

1399 .where(crit) 

1400 .select_from(dest) 

1401 .correlate_except(dest) 

1402 ) 

1403 return ex 

1404 

1405 def any(self, criterion=None, **kwargs): 

1406 """Produce an expression that tests a collection against 

1407 particular criterion, using EXISTS. 

1408 

1409 An expression like:: 

1410 

1411 session.query(MyClass).filter( 

1412 MyClass.somereference.any(SomeRelated.x==2) 

1413 ) 

1414 

1415 

1416 Will produce a query like:: 

1417 

1418 SELECT * FROM my_table WHERE 

1419 EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id 

1420 AND related.x=2) 

1421 

1422 Because :meth:`~.RelationshipProperty.Comparator.any` uses 

1423 a correlated subquery, its performance is not nearly as 

1424 good when compared against large target tables as that of 

1425 using a join. 

1426 

1427 :meth:`~.RelationshipProperty.Comparator.any` is particularly 

1428 useful for testing for empty collections:: 

1429 

1430 session.query(MyClass).filter( 

1431 ~MyClass.somereference.any() 

1432 ) 

1433 

1434 will produce:: 

1435 

1436 SELECT * FROM my_table WHERE 

1437 NOT (EXISTS (SELECT 1 FROM related WHERE 

1438 related.my_id=my_table.id)) 

1439 

1440 :meth:`~.RelationshipProperty.Comparator.any` is only 

1441 valid for collections, i.e. a :func:`_orm.relationship` 

1442 that has ``uselist=True``. For scalar references, 

1443 use :meth:`~.RelationshipProperty.Comparator.has`. 

1444 

1445 """ 

1446 if not self.property.uselist: 

1447 raise sa_exc.InvalidRequestError( 

1448 "'any()' not implemented for scalar " 

1449 "attributes. Use has()." 

1450 ) 

1451 

1452 return self._criterion_exists(criterion, **kwargs) 

1453 

1454 def has(self, criterion=None, **kwargs): 

1455 """Produce an expression that tests a scalar reference against 

1456 particular criterion, using EXISTS. 

1457 

1458 An expression like:: 

1459 

1460 session.query(MyClass).filter( 

1461 MyClass.somereference.has(SomeRelated.x==2) 

1462 ) 

1463 

1464 

1465 Will produce a query like:: 

1466 

1467 SELECT * FROM my_table WHERE 

1468 EXISTS (SELECT 1 FROM related WHERE 

1469 related.id==my_table.related_id AND related.x=2) 

1470 

1471 Because :meth:`~.RelationshipProperty.Comparator.has` uses 

1472 a correlated subquery, its performance is not nearly as 

1473 good when compared against large target tables as that of 

1474 using a join. 

1475 

1476 :meth:`~.RelationshipProperty.Comparator.has` is only 

1477 valid for scalar references, i.e. a :func:`_orm.relationship` 

1478 that has ``uselist=False``. For collection references, 

1479 use :meth:`~.RelationshipProperty.Comparator.any`. 

1480 

1481 """ 

1482 if self.property.uselist: 

1483 raise sa_exc.InvalidRequestError( 

1484 "'has()' not implemented for collections. " "Use any()." 

1485 ) 

1486 return self._criterion_exists(criterion, **kwargs) 

1487 

1488 def contains(self, other, **kwargs): 

1489 """Return a simple expression that tests a collection for 

1490 containment of a particular item. 

1491 

1492 :meth:`~.RelationshipProperty.Comparator.contains` is 

1493 only valid for a collection, i.e. a 

1494 :func:`_orm.relationship` that implements 

1495 one-to-many or many-to-many with ``uselist=True``. 

1496 

1497 When used in a simple one-to-many context, an 

1498 expression like:: 

1499 

1500 MyClass.contains(other) 

1501 

1502 Produces a clause like:: 

1503 

1504 mytable.id == <some id> 

1505 

1506 Where ``<some id>`` is the value of the foreign key 

1507 attribute on ``other`` which refers to the primary 

1508 key of its parent object. From this it follows that 

1509 :meth:`~.RelationshipProperty.Comparator.contains` is 

1510 very useful when used with simple one-to-many 

1511 operations. 

1512 

1513 For many-to-many operations, the behavior of 

1514 :meth:`~.RelationshipProperty.Comparator.contains` 

1515 has more caveats. The association table will be 

1516 rendered in the statement, producing an "implicit" 

1517 join, that is, includes multiple tables in the FROM 

1518 clause which are equated in the WHERE clause:: 

1519 

1520 query(MyClass).filter(MyClass.contains(other)) 

1521 

1522 Produces a query like:: 

1523 

1524 SELECT * FROM my_table, my_association_table AS 

1525 my_association_table_1 WHERE 

1526 my_table.id = my_association_table_1.parent_id 

1527 AND my_association_table_1.child_id = <some id> 

1528 

1529 Where ``<some id>`` would be the primary key of 

1530 ``other``. From the above, it is clear that 

1531 :meth:`~.RelationshipProperty.Comparator.contains` 

1532 will **not** work with many-to-many collections when 

1533 used in queries that move beyond simple AND 

1534 conjunctions, such as multiple 

1535 :meth:`~.RelationshipProperty.Comparator.contains` 

1536 expressions joined by OR. In such cases subqueries or 

1537 explicit "outer joins" will need to be used instead. 

1538 See :meth:`~.RelationshipProperty.Comparator.any` for 

1539 a less-performant alternative using EXISTS, or refer 

1540 to :meth:`_query.Query.outerjoin` 

1541 as well as :ref:`orm_queryguide_joins` 

1542 for more details on constructing outer joins. 

1543 

1544 kwargs may be ignored by this operator but are required for API 

1545 conformance. 

1546 """ 

1547 if not self.property.uselist: 

1548 raise sa_exc.InvalidRequestError( 

1549 "'contains' not implemented for scalar " 

1550 "attributes. Use ==" 

1551 ) 

1552 clause = self.property._optimized_compare( 

1553 other, adapt_source=self.adapter 

1554 ) 

1555 

1556 if self.property.secondaryjoin is not None: 

1557 clause.negation_clause = self.__negated_contains_or_equals( 

1558 other 

1559 ) 

1560 

1561 return clause 

1562 

1563 def __negated_contains_or_equals(self, other): 

1564 if self.property.direction == MANYTOONE: 

1565 state = attributes.instance_state(other) 

1566 

1567 def state_bindparam(local_col, state, remote_col): 

1568 dict_ = state.dict 

1569 return sql.bindparam( 

1570 local_col.key, 

1571 type_=local_col.type, 

1572 unique=True, 

1573 callable_=self.property._get_attr_w_warn_on_none( 

1574 self.property.mapper, state, dict_, remote_col 

1575 ), 

1576 ) 

1577 

1578 def adapt(col): 

1579 if self.adapter: 

1580 return self.adapter(col) 

1581 else: 

1582 return col 

1583 

1584 if self.property._use_get: 

1585 return sql.and_( 

1586 *[ 

1587 sql.or_( 

1588 adapt(x) 

1589 != state_bindparam(adapt(x), state, y), 

1590 adapt(x) == None, 

1591 ) 

1592 for (x, y) in self.property.local_remote_pairs 

1593 ] 

1594 ) 

1595 

1596 criterion = sql.and_( 

1597 *[ 

1598 x == y 

1599 for (x, y) in zip( 

1600 self.property.mapper.primary_key, 

1601 self.property.mapper.primary_key_from_instance(other), 

1602 ) 

1603 ] 

1604 ) 

1605 

1606 return ~self._criterion_exists(criterion) 

1607 

1608 def __ne__(self, other): 

1609 """Implement the ``!=`` operator. 

1610 

1611 In a many-to-one context, such as:: 

1612 

1613 MyClass.some_prop != <some object> 

1614 

1615 This will typically produce a clause such as:: 

1616 

1617 mytable.related_id != <some id> 

1618 

1619 Where ``<some id>`` is the primary key of the 

1620 given object. 

1621 

1622 The ``!=`` operator provides partial functionality for non- 

1623 many-to-one comparisons: 

1624 

1625 * Comparisons against collections are not supported. 

1626 Use 

1627 :meth:`~.RelationshipProperty.Comparator.contains` 

1628 in conjunction with :func:`_expression.not_`. 

1629 * Compared to a scalar one-to-many, will produce a 

1630 clause that compares the target columns in the parent to 

1631 the given target. 

1632 * Compared to a scalar many-to-many, an alias 

1633 of the association table will be rendered as 

1634 well, forming a natural join that is part of the 

1635 main body of the query. This will not work for 

1636 queries that go beyond simple AND conjunctions of 

1637 comparisons, such as those which use OR. Use 

1638 explicit joins, outerjoins, or 

1639 :meth:`~.RelationshipProperty.Comparator.has` in 

1640 conjunction with :func:`_expression.not_` for 

1641 more comprehensive non-many-to-one scalar 

1642 membership tests. 

1643 * Comparisons against ``None`` given in a one-to-many 

1644 or many-to-many context produce an EXISTS clause. 

1645 

1646 """ 

1647 if isinstance(other, (util.NoneType, expression.Null)): 

1648 if self.property.direction == MANYTOONE: 

1649 return _orm_annotate( 

1650 ~self.property._optimized_compare( 

1651 None, adapt_source=self.adapter 

1652 ) 

1653 ) 

1654 

1655 else: 

1656 return self._criterion_exists() 

1657 elif self.property.uselist: 

1658 raise sa_exc.InvalidRequestError( 

1659 "Can't compare a collection" 

1660 " to an object or collection; use " 

1661 "contains() to test for membership." 

1662 ) 

1663 else: 

1664 return _orm_annotate(self.__negated_contains_or_equals(other)) 

1665 

1666 @util.memoized_property 

1667 def property(self): 

1668 self.prop.parent._check_configure() 

1669 return self.prop 

1670 

1671 def _with_parent(self, instance, alias_secondary=True, from_entity=None): 

1672 assert instance is not None 

1673 adapt_source = None 

1674 if from_entity is not None: 

1675 insp = inspect(from_entity) 

1676 if insp.is_aliased_class: 

1677 adapt_source = insp._adapter.adapt_clause 

1678 return self._optimized_compare( 

1679 instance, 

1680 value_is_parent=True, 

1681 adapt_source=adapt_source, 

1682 alias_secondary=alias_secondary, 

1683 ) 

1684 

1685 def _optimized_compare( 

1686 self, 

1687 state, 

1688 value_is_parent=False, 

1689 adapt_source=None, 

1690 alias_secondary=True, 

1691 ): 

1692 if state is not None: 

1693 try: 

1694 state = inspect(state) 

1695 except sa_exc.NoInspectionAvailable: 

1696 state = None 

1697 

1698 if state is None or not getattr(state, "is_instance", False): 

1699 raise sa_exc.ArgumentError( 

1700 "Mapped instance expected for relationship " 

1701 "comparison to object. Classes, queries and other " 

1702 "SQL elements are not accepted in this context; for " 

1703 "comparison with a subquery, " 

1704 "use %s.has(**criteria)." % self 

1705 ) 

1706 reverse_direction = not value_is_parent 

1707 

1708 if state is None: 

1709 return self._lazy_none_clause( 

1710 reverse_direction, adapt_source=adapt_source 

1711 ) 

1712 

1713 if not reverse_direction: 

1714 criterion, bind_to_col = ( 

1715 self._lazy_strategy._lazywhere, 

1716 self._lazy_strategy._bind_to_col, 

1717 ) 

1718 else: 

1719 criterion, bind_to_col = ( 

1720 self._lazy_strategy._rev_lazywhere, 

1721 self._lazy_strategy._rev_bind_to_col, 

1722 ) 

1723 

1724 if reverse_direction: 

1725 mapper = self.mapper 

1726 else: 

1727 mapper = self.parent 

1728 

1729 dict_ = attributes.instance_dict(state.obj()) 

1730 

1731 def visit_bindparam(bindparam): 

1732 if bindparam._identifying_key in bind_to_col: 

1733 bindparam.callable = self._get_attr_w_warn_on_none( 

1734 mapper, 

1735 state, 

1736 dict_, 

1737 bind_to_col[bindparam._identifying_key], 

1738 ) 

1739 

1740 if self.secondary is not None and alias_secondary: 

1741 criterion = ClauseAdapter( 

1742 self.secondary._anonymous_fromclause() 

1743 ).traverse(criterion) 

1744 

1745 criterion = visitors.cloned_traverse( 

1746 criterion, {}, {"bindparam": visit_bindparam} 

1747 ) 

1748 

1749 if adapt_source: 

1750 criterion = adapt_source(criterion) 

1751 return criterion 

1752 

1753 def _get_attr_w_warn_on_none(self, mapper, state, dict_, column): 

1754 """Create the callable that is used in a many-to-one expression. 

1755 

1756 E.g.:: 

1757 

1758 u1 = s.query(User).get(5) 

1759 

1760 expr = Address.user == u1 

1761 

1762 Above, the SQL should be "address.user_id = 5". The callable 

1763 returned by this method produces the value "5" based on the identity 

1764 of ``u1``. 

1765 

1766 """ 

1767 

1768 # in this callable, we're trying to thread the needle through 

1769 # a wide variety of scenarios, including: 

1770 # 

1771 # * the object hasn't been flushed yet and there's no value for 

1772 # the attribute as of yet 

1773 # 

1774 # * the object hasn't been flushed yet but it has a user-defined 

1775 # value 

1776 # 

1777 # * the object has a value but it's expired and not locally present 

1778 # 

1779 # * the object has a value but it's expired and not locally present, 

1780 # and the object is also detached 

1781 # 

1782 # * The object hadn't been flushed yet, there was no value, but 

1783 # later, the object has been expired and detached, and *now* 

1784 # they're trying to evaluate it 

1785 # 

1786 # * the object had a value, but it was changed to a new value, and 

1787 # then expired 

1788 # 

1789 # * the object had a value, but it was changed to a new value, and 

1790 # then expired, then the object was detached 

1791 # 

1792 # * the object has a user-set value, but it's None and we don't do 

1793 # the comparison correctly for that so warn 

1794 # 

1795 

1796 prop = mapper.get_property_by_column(column) 

1797 

1798 # by invoking this method, InstanceState will track the last known 

1799 # value for this key each time the attribute is to be expired. 

1800 # this feature was added explicitly for use in this method. 

1801 state._track_last_known_value(prop.key) 

1802 

1803 def _go(): 

1804 last_known = to_return = state._last_known_values[prop.key] 

1805 existing_is_available = last_known is not attributes.NO_VALUE 

1806 

1807 # we support that the value may have changed. so here we 

1808 # try to get the most recent value including re-fetching. 

1809 # only if we can't get a value now due to detachment do we return 

1810 # the last known value 

1811 current_value = mapper._get_state_attr_by_column( 

1812 state, 

1813 dict_, 

1814 column, 

1815 passive=attributes.PASSIVE_OFF 

1816 if state.persistent 

1817 else attributes.PASSIVE_NO_FETCH ^ attributes.INIT_OK, 

1818 ) 

1819 

1820 if current_value is attributes.NEVER_SET: 

1821 if not existing_is_available: 

1822 raise sa_exc.InvalidRequestError( 

1823 "Can't resolve value for column %s on object " 

1824 "%s; no value has been set for this column" 

1825 % (column, state_str(state)) 

1826 ) 

1827 elif current_value is attributes.PASSIVE_NO_RESULT: 

1828 if not existing_is_available: 

1829 raise sa_exc.InvalidRequestError( 

1830 "Can't resolve value for column %s on object " 

1831 "%s; the object is detached and the value was " 

1832 "expired" % (column, state_str(state)) 

1833 ) 

1834 else: 

1835 to_return = current_value 

1836 if to_return is None: 

1837 util.warn( 

1838 "Got None for value of column %s; this is unsupported " 

1839 "for a relationship comparison and will not " 

1840 "currently produce an IS comparison " 

1841 "(but may in a future release)" % column 

1842 ) 

1843 return to_return 

1844 

1845 return _go 

1846 

1847 def _lazy_none_clause(self, reverse_direction=False, adapt_source=None): 

1848 if not reverse_direction: 

1849 criterion, bind_to_col = ( 

1850 self._lazy_strategy._lazywhere, 

1851 self._lazy_strategy._bind_to_col, 

1852 ) 

1853 else: 

1854 criterion, bind_to_col = ( 

1855 self._lazy_strategy._rev_lazywhere, 

1856 self._lazy_strategy._rev_bind_to_col, 

1857 ) 

1858 

1859 criterion = adapt_criterion_to_null(criterion, bind_to_col) 

1860 

1861 if adapt_source: 

1862 criterion = adapt_source(criterion) 

1863 return criterion 

1864 

1865 def __str__(self): 

1866 return str(self.parent.class_.__name__) + "." + self.key 

1867 

1868 def merge( 

1869 self, 

1870 session, 

1871 source_state, 

1872 source_dict, 

1873 dest_state, 

1874 dest_dict, 

1875 load, 

1876 _recursive, 

1877 _resolve_conflict_map, 

1878 ): 

1879 

1880 if load: 

1881 for r in self._reverse_property: 

1882 if (source_state, r) in _recursive: 

1883 return 

1884 

1885 if "merge" not in self._cascade: 

1886 return 

1887 

1888 if self.key not in source_dict: 

1889 return 

1890 

1891 if self.uselist: 

1892 impl = source_state.get_impl(self.key) 

1893 instances_iterable = impl.get_collection(source_state, source_dict) 

1894 

1895 # if this is a CollectionAttributeImpl, then empty should 

1896 # be False, otherwise "self.key in source_dict" should not be 

1897 # True 

1898 assert not instances_iterable.empty if impl.collection else True 

1899 

1900 if load: 

1901 # for a full merge, pre-load the destination collection, 

1902 # so that individual _merge of each item pulls from identity 

1903 # map for those already present. 

1904 # also assumes CollectionAttributeImpl behavior of loading 

1905 # "old" list in any case 

1906 dest_state.get_impl(self.key).get( 

1907 dest_state, dest_dict, passive=PASSIVE_MERGE 

1908 ) 

1909 

1910 dest_list = [] 

1911 for current in instances_iterable: 

1912 current_state = attributes.instance_state(current) 

1913 current_dict = attributes.instance_dict(current) 

1914 _recursive[(current_state, self)] = True 

1915 obj = session._merge( 

1916 current_state, 

1917 current_dict, 

1918 load=load, 

1919 _recursive=_recursive, 

1920 _resolve_conflict_map=_resolve_conflict_map, 

1921 ) 

1922 if obj is not None: 

1923 dest_list.append(obj) 

1924 

1925 if not load: 

1926 coll = attributes.init_state_collection( 

1927 dest_state, dest_dict, self.key 

1928 ) 

1929 for c in dest_list: 

1930 coll.append_without_event(c) 

1931 else: 

1932 dest_state.get_impl(self.key).set( 

1933 dest_state, 

1934 dest_dict, 

1935 dest_list, 

1936 _adapt=False, 

1937 passive=PASSIVE_MERGE, 

1938 ) 

1939 else: 

1940 current = source_dict[self.key] 

1941 if current is not None: 

1942 current_state = attributes.instance_state(current) 

1943 current_dict = attributes.instance_dict(current) 

1944 _recursive[(current_state, self)] = True 

1945 obj = session._merge( 

1946 current_state, 

1947 current_dict, 

1948 load=load, 

1949 _recursive=_recursive, 

1950 _resolve_conflict_map=_resolve_conflict_map, 

1951 ) 

1952 else: 

1953 obj = None 

1954 

1955 if not load: 

1956 dest_dict[self.key] = obj 

1957 else: 

1958 dest_state.get_impl(self.key).set( 

1959 dest_state, dest_dict, obj, None 

1960 ) 

1961 

1962 def _value_as_iterable( 

1963 self, state, dict_, key, passive=attributes.PASSIVE_OFF 

1964 ): 

1965 """Return a list of tuples (state, obj) for the given 

1966 key. 

1967 

1968 returns an empty list if the value is None/empty/PASSIVE_NO_RESULT 

1969 """ 

1970 

1971 impl = state.manager[key].impl 

1972 x = impl.get(state, dict_, passive=passive) 

1973 if x is attributes.PASSIVE_NO_RESULT or x is None: 

1974 return [] 

1975 elif hasattr(impl, "get_collection"): 

1976 return [ 

1977 (attributes.instance_state(o), o) 

1978 for o in impl.get_collection(state, dict_, x, passive=passive) 

1979 ] 

1980 else: 

1981 return [(attributes.instance_state(x), x)] 

1982 

1983 def cascade_iterator( 

1984 self, type_, state, dict_, visited_states, halt_on=None 

1985 ): 

1986 # assert type_ in self._cascade 

1987 

1988 # only actively lazy load on the 'delete' cascade 

1989 if type_ != "delete" or self.passive_deletes: 

1990 passive = attributes.PASSIVE_NO_INITIALIZE 

1991 else: 

1992 passive = attributes.PASSIVE_OFF 

1993 

1994 if type_ == "save-update": 

1995 tuples = state.manager[self.key].impl.get_all_pending(state, dict_) 

1996 

1997 else: 

1998 tuples = self._value_as_iterable( 

1999 state, dict_, self.key, passive=passive 

2000 ) 

2001 

2002 skip_pending = ( 

2003 type_ == "refresh-expire" and "delete-orphan" not in self._cascade 

2004 ) 

2005 

2006 for instance_state, c in tuples: 

2007 if instance_state in visited_states: 

2008 continue 

2009 

2010 if c is None: 

2011 # would like to emit a warning here, but 

2012 # would not be consistent with collection.append(None) 

2013 # current behavior of silently skipping. 

2014 # see [ticket:2229] 

2015 continue 

2016 

2017 instance_dict = attributes.instance_dict(c) 

2018 

2019 if halt_on and halt_on(instance_state): 

2020 continue 

2021 

2022 if skip_pending and not instance_state.key: 

2023 continue 

2024 

2025 instance_mapper = instance_state.manager.mapper 

2026 

2027 if not instance_mapper.isa(self.mapper.class_manager.mapper): 

2028 raise AssertionError( 

2029 "Attribute '%s' on class '%s' " 

2030 "doesn't handle objects " 

2031 "of type '%s'" 

2032 % (self.key, self.parent.class_, c.__class__) 

2033 ) 

2034 

2035 visited_states.add(instance_state) 

2036 

2037 yield c, instance_mapper, instance_state, instance_dict 

2038 

2039 @property 

2040 def _effective_sync_backref(self): 

2041 if self.viewonly: 

2042 return False 

2043 else: 

2044 return self.sync_backref is not False 

2045 

2046 @staticmethod 

2047 def _check_sync_backref(rel_a, rel_b): 

2048 if rel_a.viewonly and rel_b.sync_backref: 

2049 raise sa_exc.InvalidRequestError( 

2050 "Relationship %s cannot specify sync_backref=True since %s " 

2051 "includes viewonly=True." % (rel_b, rel_a) 

2052 ) 

2053 if ( 

2054 rel_a.viewonly 

2055 and not rel_b.viewonly 

2056 and rel_b.sync_backref is not False 

2057 ): 

2058 rel_b.sync_backref = False 

2059 

2060 def _add_reverse_property(self, key): 

2061 other = self.mapper.get_property(key, _configure_mappers=False) 

2062 if not isinstance(other, RelationshipProperty): 

2063 raise sa_exc.InvalidRequestError( 

2064 "back_populates on relationship '%s' refers to attribute '%s' " 

2065 "that is not a relationship. The back_populates parameter " 

2066 "should refer to the name of a relationship on the target " 

2067 "class." % (self, other) 

2068 ) 

2069 # viewonly and sync_backref cases 

2070 # 1. self.viewonly==True and other.sync_backref==True -> error 

2071 # 2. self.viewonly==True and other.viewonly==False and 

2072 # other.sync_backref==None -> warn sync_backref=False, set to False 

2073 self._check_sync_backref(self, other) 

2074 # 3. other.viewonly==True and self.sync_backref==True -> error 

2075 # 4. other.viewonly==True and self.viewonly==False and 

2076 # self.sync_backref==None -> warn sync_backref=False, set to False 

2077 self._check_sync_backref(other, self) 

2078 

2079 self._reverse_property.add(other) 

2080 other._reverse_property.add(self) 

2081 

2082 if not other.mapper.common_parent(self.parent): 

2083 raise sa_exc.ArgumentError( 

2084 "reverse_property %r on " 

2085 "relationship %s references relationship %s, which " 

2086 "does not reference mapper %s" 

2087 % (key, self, other, self.parent) 

2088 ) 

2089 

2090 if ( 

2091 self.direction in (ONETOMANY, MANYTOONE) 

2092 and self.direction == other.direction 

2093 ): 

2094 raise sa_exc.ArgumentError( 

2095 "%s and back-reference %s are " 

2096 "both of the same direction %r. Did you mean to " 

2097 "set remote_side on the many-to-one side ?" 

2098 % (other, self, self.direction) 

2099 ) 

2100 

2101 @util.memoized_property 

2102 @util.preload_module("sqlalchemy.orm.mapper") 

2103 def entity(self): 

2104 """Return the target mapped entity, which is an inspect() of the 

2105 class or aliased class that is referred towards. 

2106 

2107 """ 

2108 

2109 mapperlib = util.preloaded.orm_mapper 

2110 

2111 if isinstance(self.argument, util.string_types): 

2112 argument = self._clsregistry_resolve_name(self.argument)() 

2113 

2114 elif callable(self.argument) and not isinstance( 

2115 self.argument, (type, mapperlib.Mapper) 

2116 ): 

2117 argument = self.argument() 

2118 else: 

2119 argument = self.argument 

2120 

2121 if isinstance(argument, type): 

2122 return mapperlib.class_mapper(argument, configure=False) 

2123 

2124 try: 

2125 entity = inspect(argument) 

2126 except sa_exc.NoInspectionAvailable: 

2127 pass 

2128 else: 

2129 if hasattr(entity, "mapper"): 

2130 return entity 

2131 

2132 raise sa_exc.ArgumentError( 

2133 "relationship '%s' expects " 

2134 "a class or a mapper argument (received: %s)" 

2135 % (self.key, type(argument)) 

2136 ) 

2137 

2138 @util.memoized_property 

2139 def mapper(self): 

2140 """Return the targeted :class:`_orm.Mapper` for this 

2141 :class:`.RelationshipProperty`. 

2142 

2143 This is a lazy-initializing static attribute. 

2144 

2145 """ 

2146 return self.entity.mapper 

2147 

2148 def do_init(self): 

2149 self._check_conflicts() 

2150 self._process_dependent_arguments() 

2151 self._setup_registry_dependencies() 

2152 self._setup_join_conditions() 

2153 self._check_cascade_settings(self._cascade) 

2154 self._post_init() 

2155 self._generate_backref() 

2156 self._join_condition._warn_for_conflicting_sync_targets() 

2157 super(RelationshipProperty, self).do_init() 

2158 self._lazy_strategy = self._get_strategy((("lazy", "select"),)) 

2159 

2160 def _setup_registry_dependencies(self): 

2161 self.parent.mapper.registry._set_depends_on( 

2162 self.entity.mapper.registry 

2163 ) 

2164 

2165 def _process_dependent_arguments(self): 

2166 """Convert incoming configuration arguments to their 

2167 proper form. 

2168 

2169 Callables are resolved, ORM annotations removed. 

2170 

2171 """ 

2172 

2173 # accept callables for other attributes which may require 

2174 # deferred initialization. This technique is used 

2175 # by declarative "string configs" and some recipes. 

2176 for attr in ( 

2177 "order_by", 

2178 "primaryjoin", 

2179 "secondaryjoin", 

2180 "secondary", 

2181 "_user_defined_foreign_keys", 

2182 "remote_side", 

2183 ): 

2184 attr_value = getattr(self, attr) 

2185 

2186 if isinstance(attr_value, util.string_types): 

2187 setattr( 

2188 self, 

2189 attr, 

2190 self._clsregistry_resolve_arg( 

2191 attr_value, favor_tables=attr == "secondary" 

2192 )(), 

2193 ) 

2194 elif callable(attr_value) and not _is_mapped_class(attr_value): 

2195 setattr(self, attr, attr_value()) 

2196 

2197 # remove "annotations" which are present if mapped class 

2198 # descriptors are used to create the join expression. 

2199 for attr in "primaryjoin", "secondaryjoin": 

2200 val = getattr(self, attr) 

2201 if val is not None: 

2202 setattr( 

2203 self, 

2204 attr, 

2205 _orm_deannotate( 

2206 coercions.expect( 

2207 roles.ColumnArgumentRole, val, argname=attr 

2208 ) 

2209 ), 

2210 ) 

2211 

2212 if self.secondary is not None and _is_mapped_class(self.secondary): 

2213 raise sa_exc.ArgumentError( 

2214 "secondary argument %s passed to to relationship() %s must " 

2215 "be a Table object or other FROM clause; can't send a mapped " 

2216 "class directly as rows in 'secondary' are persisted " 

2217 "independently of a class that is mapped " 

2218 "to that same table." % (self.secondary, self) 

2219 ) 

2220 

2221 # ensure expressions in self.order_by, foreign_keys, 

2222 # remote_side are all columns, not strings. 

2223 if self.order_by is not False and self.order_by is not None: 

2224 self.order_by = tuple( 

2225 coercions.expect( 

2226 roles.ColumnArgumentRole, x, argname="order_by" 

2227 ) 

2228 for x in util.to_list(self.order_by) 

2229 ) 

2230 

2231 self._user_defined_foreign_keys = util.column_set( 

2232 coercions.expect( 

2233 roles.ColumnArgumentRole, x, argname="foreign_keys" 

2234 ) 

2235 for x in util.to_column_set(self._user_defined_foreign_keys) 

2236 ) 

2237 

2238 self.remote_side = util.column_set( 

2239 coercions.expect( 

2240 roles.ColumnArgumentRole, x, argname="remote_side" 

2241 ) 

2242 for x in util.to_column_set(self.remote_side) 

2243 ) 

2244 

2245 self.target = self.entity.persist_selectable 

2246 

2247 def _setup_join_conditions(self): 

2248 self._join_condition = jc = JoinCondition( 

2249 parent_persist_selectable=self.parent.persist_selectable, 

2250 child_persist_selectable=self.entity.persist_selectable, 

2251 parent_local_selectable=self.parent.local_table, 

2252 child_local_selectable=self.entity.local_table, 

2253 primaryjoin=self.primaryjoin, 

2254 secondary=self.secondary, 

2255 secondaryjoin=self.secondaryjoin, 

2256 parent_equivalents=self.parent._equivalent_columns, 

2257 child_equivalents=self.mapper._equivalent_columns, 

2258 consider_as_foreign_keys=self._user_defined_foreign_keys, 

2259 local_remote_pairs=self.local_remote_pairs, 

2260 remote_side=self.remote_side, 

2261 self_referential=self._is_self_referential, 

2262 prop=self, 

2263 support_sync=not self.viewonly, 

2264 can_be_synced_fn=self._columns_are_mapped, 

2265 ) 

2266 self.primaryjoin = jc.primaryjoin 

2267 self.secondaryjoin = jc.secondaryjoin 

2268 self.direction = jc.direction 

2269 self.local_remote_pairs = jc.local_remote_pairs 

2270 self.remote_side = jc.remote_columns 

2271 self.local_columns = jc.local_columns 

2272 self.synchronize_pairs = jc.synchronize_pairs 

2273 self._calculated_foreign_keys = jc.foreign_key_columns 

2274 self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs 

2275 

2276 @property 

2277 def _clsregistry_resolve_arg(self): 

2278 return self._clsregistry_resolvers[1] 

2279 

2280 @property 

2281 def _clsregistry_resolve_name(self): 

2282 return self._clsregistry_resolvers[0] 

2283 

2284 @util.memoized_property 

2285 @util.preload_module("sqlalchemy.orm.clsregistry") 

2286 def _clsregistry_resolvers(self): 

2287 _resolver = util.preloaded.orm_clsregistry._resolver 

2288 

2289 return _resolver(self.parent.class_, self) 

2290 

2291 @util.preload_module("sqlalchemy.orm.mapper") 

2292 def _check_conflicts(self): 

2293 """Test that this relationship is legal, warn about 

2294 inheritance conflicts.""" 

2295 mapperlib = util.preloaded.orm_mapper 

2296 if self.parent.non_primary and not mapperlib.class_mapper( 

2297 self.parent.class_, configure=False 

2298 ).has_property(self.key): 

2299 raise sa_exc.ArgumentError( 

2300 "Attempting to assign a new " 

2301 "relationship '%s' to a non-primary mapper on " 

2302 "class '%s'. New relationships can only be added " 

2303 "to the primary mapper, i.e. the very first mapper " 

2304 "created for class '%s' " 

2305 % ( 

2306 self.key, 

2307 self.parent.class_.__name__, 

2308 self.parent.class_.__name__, 

2309 ) 

2310 ) 

2311 

2312 @property 

2313 def cascade(self): 

2314 """Return the current cascade setting for this 

2315 :class:`.RelationshipProperty`. 

2316 """ 

2317 return self._cascade 

2318 

2319 @cascade.setter 

2320 def cascade(self, cascade): 

2321 self._set_cascade(cascade) 

2322 

2323 def _set_cascade(self, cascade): 

2324 cascade = CascadeOptions(cascade) 

2325 

2326 if self.viewonly: 

2327 non_viewonly = set(cascade).difference( 

2328 CascadeOptions._viewonly_cascades 

2329 ) 

2330 if non_viewonly: 

2331 raise sa_exc.ArgumentError( 

2332 'Cascade settings "%s" apply to persistence operations ' 

2333 "and should not be combined with a viewonly=True " 

2334 "relationship." % (", ".join(sorted(non_viewonly))) 

2335 ) 

2336 

2337 if "mapper" in self.__dict__: 

2338 self._check_cascade_settings(cascade) 

2339 self._cascade = cascade 

2340 

2341 if self._dependency_processor: 

2342 self._dependency_processor.cascade = cascade 

2343 

2344 def _check_cascade_settings(self, cascade): 

2345 if ( 

2346 cascade.delete_orphan 

2347 and not self.single_parent 

2348 and (self.direction is MANYTOMANY or self.direction is MANYTOONE) 

2349 ): 

2350 raise sa_exc.ArgumentError( 

2351 "For %(direction)s relationship %(rel)s, delete-orphan " 

2352 "cascade is normally " 

2353 'configured only on the "one" side of a one-to-many ' 

2354 "relationship, " 

2355 'and not on the "many" side of a many-to-one or many-to-many ' 

2356 "relationship. " 

2357 "To force this relationship to allow a particular " 

2358 '"%(relatedcls)s" object to be referred towards by only ' 

2359 'a single "%(clsname)s" object at a time via the ' 

2360 "%(rel)s relationship, which " 

2361 "would allow " 

2362 "delete-orphan cascade to take place in this direction, set " 

2363 "the single_parent=True flag." 

2364 % { 

2365 "rel": self, 

2366 "direction": "many-to-one" 

2367 if self.direction is MANYTOONE 

2368 else "many-to-many", 

2369 "clsname": self.parent.class_.__name__, 

2370 "relatedcls": self.mapper.class_.__name__, 

2371 }, 

2372 code="bbf0", 

2373 ) 

2374 

2375 if self.passive_deletes == "all" and ( 

2376 "delete" in cascade or "delete-orphan" in cascade 

2377 ): 

2378 raise sa_exc.ArgumentError( 

2379 "On %s, can't set passive_deletes='all' in conjunction " 

2380 "with 'delete' or 'delete-orphan' cascade" % self 

2381 ) 

2382 

2383 if cascade.delete_orphan: 

2384 self.mapper.primary_mapper()._delete_orphans.append( 

2385 (self.key, self.parent.class_) 

2386 ) 

2387 

2388 def _persists_for(self, mapper): 

2389 """Return True if this property will persist values on behalf 

2390 of the given mapper. 

2391 

2392 """ 

2393 

2394 return ( 

2395 self.key in mapper.relationships 

2396 and mapper.relationships[self.key] is self 

2397 ) 

2398 

2399 def _columns_are_mapped(self, *cols): 

2400 """Return True if all columns in the given collection are 

2401 mapped by the tables referenced by this :class:`.Relationship`. 

2402 

2403 """ 

2404 for c in cols: 

2405 if ( 

2406 self.secondary is not None 

2407 and self.secondary.c.contains_column(c) 

2408 ): 

2409 continue 

2410 if not self.parent.persist_selectable.c.contains_column( 

2411 c 

2412 ) and not self.target.c.contains_column(c): 

2413 return False 

2414 return True 

2415 

2416 def _generate_backref(self): 

2417 """Interpret the 'backref' instruction to create a 

2418 :func:`_orm.relationship` complementary to this one.""" 

2419 

2420 if self.parent.non_primary: 

2421 return 

2422 if self.backref is not None and not self.back_populates: 

2423 if isinstance(self.backref, util.string_types): 

2424 backref_key, kwargs = self.backref, {} 

2425 else: 

2426 backref_key, kwargs = self.backref 

2427 mapper = self.mapper.primary_mapper() 

2428 

2429 if not mapper.concrete: 

2430 check = set(mapper.iterate_to_root()).union( 

2431 mapper.self_and_descendants 

2432 ) 

2433 for m in check: 

2434 if m.has_property(backref_key) and not m.concrete: 

2435 raise sa_exc.ArgumentError( 

2436 "Error creating backref " 

2437 "'%s' on relationship '%s': property of that " 

2438 "name exists on mapper '%s'" 

2439 % (backref_key, self, m) 

2440 ) 

2441 

2442 # determine primaryjoin/secondaryjoin for the 

2443 # backref. Use the one we had, so that 

2444 # a custom join doesn't have to be specified in 

2445 # both directions. 

2446 if self.secondary is not None: 

2447 # for many to many, just switch primaryjoin/ 

2448 # secondaryjoin. use the annotated 

2449 # pj/sj on the _join_condition. 

2450 pj = kwargs.pop( 

2451 "primaryjoin", 

2452 self._join_condition.secondaryjoin_minus_local, 

2453 ) 

2454 sj = kwargs.pop( 

2455 "secondaryjoin", 

2456 self._join_condition.primaryjoin_minus_local, 

2457 ) 

2458 else: 

2459 pj = kwargs.pop( 

2460 "primaryjoin", 

2461 self._join_condition.primaryjoin_reverse_remote, 

2462 ) 

2463 sj = kwargs.pop("secondaryjoin", None) 

2464 if sj: 

2465 raise sa_exc.InvalidRequestError( 

2466 "Can't assign 'secondaryjoin' on a backref " 

2467 "against a non-secondary relationship." 

2468 ) 

2469 

2470 foreign_keys = kwargs.pop( 

2471 "foreign_keys", self._user_defined_foreign_keys 

2472 ) 

2473 parent = self.parent.primary_mapper() 

2474 kwargs.setdefault("viewonly", self.viewonly) 

2475 kwargs.setdefault("post_update", self.post_update) 

2476 kwargs.setdefault("passive_updates", self.passive_updates) 

2477 kwargs.setdefault("sync_backref", self.sync_backref) 

2478 self.back_populates = backref_key 

2479 relationship = RelationshipProperty( 

2480 parent, 

2481 self.secondary, 

2482 pj, 

2483 sj, 

2484 foreign_keys=foreign_keys, 

2485 back_populates=self.key, 

2486 **kwargs 

2487 ) 

2488 mapper._configure_property(backref_key, relationship) 

2489 

2490 if self.back_populates: 

2491 self._add_reverse_property(self.back_populates) 

2492 

2493 @util.preload_module("sqlalchemy.orm.dependency") 

2494 def _post_init(self): 

2495 dependency = util.preloaded.orm_dependency 

2496 

2497 if self.uselist is None: 

2498 self.uselist = self.direction is not MANYTOONE 

2499 if not self.viewonly: 

2500 self._dependency_processor = ( 

2501 dependency.DependencyProcessor.from_relationship 

2502 )(self) 

2503 

2504 @util.memoized_property 

2505 def _use_get(self): 

2506 """memoize the 'use_get' attribute of this RelationshipLoader's 

2507 lazyloader.""" 

2508 

2509 strategy = self._lazy_strategy 

2510 return strategy.use_get 

2511 

2512 @util.memoized_property 

2513 def _is_self_referential(self): 

2514 return self.mapper.common_parent(self.parent) 

2515 

2516 def _create_joins( 

2517 self, 

2518 source_polymorphic=False, 

2519 source_selectable=None, 

2520 dest_selectable=None, 

2521 of_type_entity=None, 

2522 alias_secondary=False, 

2523 extra_criteria=(), 

2524 ): 

2525 

2526 aliased = False 

2527 

2528 if alias_secondary and self.secondary is not None: 

2529 aliased = True 

2530 

2531 if source_selectable is None: 

2532 if source_polymorphic and self.parent.with_polymorphic: 

2533 source_selectable = self.parent._with_polymorphic_selectable 

2534 

2535 if of_type_entity: 

2536 dest_mapper = of_type_entity.mapper 

2537 if dest_selectable is None: 

2538 dest_selectable = of_type_entity.selectable 

2539 aliased = True 

2540 else: 

2541 dest_mapper = self.mapper 

2542 

2543 if dest_selectable is None: 

2544 dest_selectable = self.entity.selectable 

2545 if self.mapper.with_polymorphic: 

2546 aliased = True 

2547 

2548 if self._is_self_referential and source_selectable is None: 

2549 dest_selectable = dest_selectable._anonymous_fromclause() 

2550 aliased = True 

2551 elif ( 

2552 dest_selectable is not self.mapper._with_polymorphic_selectable 

2553 or self.mapper.with_polymorphic 

2554 ): 

2555 aliased = True 

2556 

2557 single_crit = dest_mapper._single_table_criterion 

2558 aliased = aliased or ( 

2559 source_selectable is not None 

2560 and ( 

2561 source_selectable 

2562 is not self.parent._with_polymorphic_selectable 

2563 or source_selectable._is_subquery 

2564 ) 

2565 ) 

2566 

2567 ( 

2568 primaryjoin, 

2569 secondaryjoin, 

2570 secondary, 

2571 target_adapter, 

2572 dest_selectable, 

2573 ) = self._join_condition.join_targets( 

2574 source_selectable, 

2575 dest_selectable, 

2576 aliased, 

2577 single_crit, 

2578 extra_criteria, 

2579 ) 

2580 if source_selectable is None: 

2581 source_selectable = self.parent.local_table 

2582 if dest_selectable is None: 

2583 dest_selectable = self.entity.local_table 

2584 return ( 

2585 primaryjoin, 

2586 secondaryjoin, 

2587 source_selectable, 

2588 dest_selectable, 

2589 secondary, 

2590 target_adapter, 

2591 ) 

2592 

2593 

2594def _annotate_columns(element, annotations): 

2595 def clone(elem): 

2596 if isinstance(elem, expression.ColumnClause): 

2597 elem = elem._annotate(annotations.copy()) 

2598 elem._copy_internals(clone=clone) 

2599 return elem 

2600 

2601 if element is not None: 

2602 element = clone(element) 

2603 clone = None # remove gc cycles 

2604 return element 

2605 

2606 

2607class JoinCondition(object): 

2608 def __init__( 

2609 self, 

2610 parent_persist_selectable, 

2611 child_persist_selectable, 

2612 parent_local_selectable, 

2613 child_local_selectable, 

2614 primaryjoin=None, 

2615 secondary=None, 

2616 secondaryjoin=None, 

2617 parent_equivalents=None, 

2618 child_equivalents=None, 

2619 consider_as_foreign_keys=None, 

2620 local_remote_pairs=None, 

2621 remote_side=None, 

2622 self_referential=False, 

2623 prop=None, 

2624 support_sync=True, 

2625 can_be_synced_fn=lambda *c: True, 

2626 ): 

2627 self.parent_persist_selectable = parent_persist_selectable 

2628 self.parent_local_selectable = parent_local_selectable 

2629 self.child_persist_selectable = child_persist_selectable 

2630 self.child_local_selectable = child_local_selectable 

2631 self.parent_equivalents = parent_equivalents 

2632 self.child_equivalents = child_equivalents 

2633 self.primaryjoin = primaryjoin 

2634 self.secondaryjoin = secondaryjoin 

2635 self.secondary = secondary 

2636 self.consider_as_foreign_keys = consider_as_foreign_keys 

2637 self._local_remote_pairs = local_remote_pairs 

2638 self._remote_side = remote_side 

2639 self.prop = prop 

2640 self.self_referential = self_referential 

2641 self.support_sync = support_sync 

2642 self.can_be_synced_fn = can_be_synced_fn 

2643 self._determine_joins() 

2644 self._sanitize_joins() 

2645 self._annotate_fks() 

2646 self._annotate_remote() 

2647 self._annotate_local() 

2648 self._annotate_parentmapper() 

2649 self._setup_pairs() 

2650 self._check_foreign_cols(self.primaryjoin, True) 

2651 if self.secondaryjoin is not None: 

2652 self._check_foreign_cols(self.secondaryjoin, False) 

2653 self._determine_direction() 

2654 self._check_remote_side() 

2655 self._log_joins() 

2656 

2657 def _log_joins(self): 

2658 if self.prop is None: 

2659 return 

2660 log = self.prop.logger 

2661 log.info("%s setup primary join %s", self.prop, self.primaryjoin) 

2662 log.info("%s setup secondary join %s", self.prop, self.secondaryjoin) 

2663 log.info( 

2664 "%s synchronize pairs [%s]", 

2665 self.prop, 

2666 ",".join( 

2667 "(%s => %s)" % (l, r) for (l, r) in self.synchronize_pairs 

2668 ), 

2669 ) 

2670 log.info( 

2671 "%s secondary synchronize pairs [%s]", 

2672 self.prop, 

2673 ",".join( 

2674 "(%s => %s)" % (l, r) 

2675 for (l, r) in self.secondary_synchronize_pairs or [] 

2676 ), 

2677 ) 

2678 log.info( 

2679 "%s local/remote pairs [%s]", 

2680 self.prop, 

2681 ",".join( 

2682 "(%s / %s)" % (l, r) for (l, r) in self.local_remote_pairs 

2683 ), 

2684 ) 

2685 log.info( 

2686 "%s remote columns [%s]", 

2687 self.prop, 

2688 ",".join("%s" % col for col in self.remote_columns), 

2689 ) 

2690 log.info( 

2691 "%s local columns [%s]", 

2692 self.prop, 

2693 ",".join("%s" % col for col in self.local_columns), 

2694 ) 

2695 log.info("%s relationship direction %s", self.prop, self.direction) 

2696 

2697 def _sanitize_joins(self): 

2698 """remove the parententity annotation from our join conditions which 

2699 can leak in here based on some declarative patterns and maybe others. 

2700 

2701 We'd want to remove "parentmapper" also, but apparently there's 

2702 an exotic use case in _join_fixture_inh_selfref_w_entity 

2703 that relies upon it being present, see :ticket:`3364`. 

2704 

2705 """ 

2706 

2707 self.primaryjoin = _deep_deannotate( 

2708 self.primaryjoin, values=("parententity", "proxy_key") 

2709 ) 

2710 if self.secondaryjoin is not None: 

2711 self.secondaryjoin = _deep_deannotate( 

2712 self.secondaryjoin, values=("parententity", "proxy_key") 

2713 ) 

2714 

2715 def _determine_joins(self): 

2716 """Determine the 'primaryjoin' and 'secondaryjoin' attributes, 

2717 if not passed to the constructor already. 

2718 

2719 This is based on analysis of the foreign key relationships 

2720 between the parent and target mapped selectables. 

2721 

2722 """ 

2723 if self.secondaryjoin is not None and self.secondary is None: 

2724 raise sa_exc.ArgumentError( 

2725 "Property %s specified with secondary " 

2726 "join condition but " 

2727 "no secondary argument" % self.prop 

2728 ) 

2729 

2730 # find a join between the given mapper's mapped table and 

2731 # the given table. will try the mapper's local table first 

2732 # for more specificity, then if not found will try the more 

2733 # general mapped table, which in the case of inheritance is 

2734 # a join. 

2735 try: 

2736 consider_as_foreign_keys = self.consider_as_foreign_keys or None 

2737 if self.secondary is not None: 

2738 if self.secondaryjoin is None: 

2739 self.secondaryjoin = join_condition( 

2740 self.child_persist_selectable, 

2741 self.secondary, 

2742 a_subset=self.child_local_selectable, 

2743 consider_as_foreign_keys=consider_as_foreign_keys, 

2744 ) 

2745 if self.primaryjoin is None: 

2746 self.primaryjoin = join_condition( 

2747 self.parent_persist_selectable, 

2748 self.secondary, 

2749 a_subset=self.parent_local_selectable, 

2750 consider_as_foreign_keys=consider_as_foreign_keys, 

2751 ) 

2752 else: 

2753 if self.primaryjoin is None: 

2754 self.primaryjoin = join_condition( 

2755 self.parent_persist_selectable, 

2756 self.child_persist_selectable, 

2757 a_subset=self.parent_local_selectable, 

2758 consider_as_foreign_keys=consider_as_foreign_keys, 

2759 ) 

2760 except sa_exc.NoForeignKeysError as nfe: 

2761 if self.secondary is not None: 

2762 util.raise_( 

2763 sa_exc.NoForeignKeysError( 

2764 "Could not determine join " 

2765 "condition between parent/child tables on " 

2766 "relationship %s - there are no foreign keys " 

2767 "linking these tables via secondary table '%s'. " 

2768 "Ensure that referencing columns are associated " 

2769 "with a ForeignKey or ForeignKeyConstraint, or " 

2770 "specify 'primaryjoin' and 'secondaryjoin' " 

2771 "expressions." % (self.prop, self.secondary) 

2772 ), 

2773 from_=nfe, 

2774 ) 

2775 else: 

2776 util.raise_( 

2777 sa_exc.NoForeignKeysError( 

2778 "Could not determine join " 

2779 "condition between parent/child tables on " 

2780 "relationship %s - there are no foreign keys " 

2781 "linking these tables. " 

2782 "Ensure that referencing columns are associated " 

2783 "with a ForeignKey or ForeignKeyConstraint, or " 

2784 "specify a 'primaryjoin' expression." % self.prop 

2785 ), 

2786 from_=nfe, 

2787 ) 

2788 except sa_exc.AmbiguousForeignKeysError as afe: 

2789 if self.secondary is not None: 

2790 util.raise_( 

2791 sa_exc.AmbiguousForeignKeysError( 

2792 "Could not determine join " 

2793 "condition between parent/child tables on " 

2794 "relationship %s - there are multiple foreign key " 

2795 "paths linking the tables via secondary table '%s'. " 

2796 "Specify the 'foreign_keys' " 

2797 "argument, providing a list of those columns which " 

2798 "should be counted as containing a foreign key " 

2799 "reference from the secondary table to each of the " 

2800 "parent and child tables." 

2801 % (self.prop, self.secondary) 

2802 ), 

2803 from_=afe, 

2804 ) 

2805 else: 

2806 util.raise_( 

2807 sa_exc.AmbiguousForeignKeysError( 

2808 "Could not determine join " 

2809 "condition between parent/child tables on " 

2810 "relationship %s - there are multiple foreign key " 

2811 "paths linking the tables. Specify the " 

2812 "'foreign_keys' argument, providing a list of those " 

2813 "columns which should be counted as containing a " 

2814 "foreign key reference to the parent table." 

2815 % self.prop 

2816 ), 

2817 from_=afe, 

2818 ) 

2819 

2820 @property 

2821 def primaryjoin_minus_local(self): 

2822 return _deep_deannotate(self.primaryjoin, values=("local", "remote")) 

2823 

2824 @property 

2825 def secondaryjoin_minus_local(self): 

2826 return _deep_deannotate(self.secondaryjoin, values=("local", "remote")) 

2827 

2828 @util.memoized_property 

2829 def primaryjoin_reverse_remote(self): 

2830 """Return the primaryjoin condition suitable for the 

2831 "reverse" direction. 

2832 

2833 If the primaryjoin was delivered here with pre-existing 

2834 "remote" annotations, the local/remote annotations 

2835 are reversed. Otherwise, the local/remote annotations 

2836 are removed. 

2837 

2838 """ 

2839 if self._has_remote_annotations: 

2840 

2841 def replace(element): 

2842 if "remote" in element._annotations: 

2843 v = dict(element._annotations) 

2844 del v["remote"] 

2845 v["local"] = True 

2846 return element._with_annotations(v) 

2847 elif "local" in element._annotations: 

2848 v = dict(element._annotations) 

2849 del v["local"] 

2850 v["remote"] = True 

2851 return element._with_annotations(v) 

2852 

2853 return visitors.replacement_traverse(self.primaryjoin, {}, replace) 

2854 else: 

2855 if self._has_foreign_annotations: 

2856 # TODO: coverage 

2857 return _deep_deannotate( 

2858 self.primaryjoin, values=("local", "remote") 

2859 ) 

2860 else: 

2861 return _deep_deannotate(self.primaryjoin) 

2862 

2863 def _has_annotation(self, clause, annotation): 

2864 for col in visitors.iterate(clause, {}): 

2865 if annotation in col._annotations: 

2866 return True 

2867 else: 

2868 return False 

2869 

2870 @util.memoized_property 

2871 def _has_foreign_annotations(self): 

2872 return self._has_annotation(self.primaryjoin, "foreign") 

2873 

2874 @util.memoized_property 

2875 def _has_remote_annotations(self): 

2876 return self._has_annotation(self.primaryjoin, "remote") 

2877 

2878 def _annotate_fks(self): 

2879 """Annotate the primaryjoin and secondaryjoin 

2880 structures with 'foreign' annotations marking columns 

2881 considered as foreign. 

2882 

2883 """ 

2884 if self._has_foreign_annotations: 

2885 return 

2886 

2887 if self.consider_as_foreign_keys: 

2888 self._annotate_from_fk_list() 

2889 else: 

2890 self._annotate_present_fks() 

2891 

2892 def _annotate_from_fk_list(self): 

2893 def check_fk(col): 

2894 if col in self.consider_as_foreign_keys: 

2895 return col._annotate({"foreign": True}) 

2896 

2897 self.primaryjoin = visitors.replacement_traverse( 

2898 self.primaryjoin, {}, check_fk 

2899 ) 

2900 if self.secondaryjoin is not None: 

2901 self.secondaryjoin = visitors.replacement_traverse( 

2902 self.secondaryjoin, {}, check_fk 

2903 ) 

2904 

2905 def _annotate_present_fks(self): 

2906 if self.secondary is not None: 

2907 secondarycols = util.column_set(self.secondary.c) 

2908 else: 

2909 secondarycols = set() 

2910 

2911 def is_foreign(a, b): 

2912 if isinstance(a, schema.Column) and isinstance(b, schema.Column): 

2913 if a.references(b): 

2914 return a 

2915 elif b.references(a): 

2916 return b 

2917 

2918 if secondarycols: 

2919 if a in secondarycols and b not in secondarycols: 

2920 return a 

2921 elif b in secondarycols and a not in secondarycols: 

2922 return b 

2923 

2924 def visit_binary(binary): 

2925 if not isinstance( 

2926 binary.left, sql.ColumnElement 

2927 ) or not isinstance(binary.right, sql.ColumnElement): 

2928 return 

2929 

2930 if ( 

2931 "foreign" not in binary.left._annotations 

2932 and "foreign" not in binary.right._annotations 

2933 ): 

2934 col = is_foreign(binary.left, binary.right) 

2935 if col is not None: 

2936 if col.compare(binary.left): 

2937 binary.left = binary.left._annotate({"foreign": True}) 

2938 elif col.compare(binary.right): 

2939 binary.right = binary.right._annotate( 

2940 {"foreign": True} 

2941 ) 

2942 

2943 self.primaryjoin = visitors.cloned_traverse( 

2944 self.primaryjoin, {}, {"binary": visit_binary} 

2945 ) 

2946 if self.secondaryjoin is not None: 

2947 self.secondaryjoin = visitors.cloned_traverse( 

2948 self.secondaryjoin, {}, {"binary": visit_binary} 

2949 ) 

2950 

2951 def _refers_to_parent_table(self): 

2952 """Return True if the join condition contains column 

2953 comparisons where both columns are in both tables. 

2954 

2955 """ 

2956 pt = self.parent_persist_selectable 

2957 mt = self.child_persist_selectable 

2958 result = [False] 

2959 

2960 def visit_binary(binary): 

2961 c, f = binary.left, binary.right 

2962 if ( 

2963 isinstance(c, expression.ColumnClause) 

2964 and isinstance(f, expression.ColumnClause) 

2965 and pt.is_derived_from(c.table) 

2966 and pt.is_derived_from(f.table) 

2967 and mt.is_derived_from(c.table) 

2968 and mt.is_derived_from(f.table) 

2969 ): 

2970 result[0] = True 

2971 

2972 visitors.traverse(self.primaryjoin, {}, {"binary": visit_binary}) 

2973 return result[0] 

2974 

2975 def _tables_overlap(self): 

2976 """Return True if parent/child tables have some overlap.""" 

2977 

2978 return selectables_overlap( 

2979 self.parent_persist_selectable, self.child_persist_selectable 

2980 ) 

2981 

2982 def _annotate_remote(self): 

2983 """Annotate the primaryjoin and secondaryjoin 

2984 structures with 'remote' annotations marking columns 

2985 considered as part of the 'remote' side. 

2986 

2987 """ 

2988 if self._has_remote_annotations: 

2989 return 

2990 

2991 if self.secondary is not None: 

2992 self._annotate_remote_secondary() 

2993 elif self._local_remote_pairs or self._remote_side: 

2994 self._annotate_remote_from_args() 

2995 elif self._refers_to_parent_table(): 

2996 self._annotate_selfref( 

2997 lambda col: "foreign" in col._annotations, False 

2998 ) 

2999 elif self._tables_overlap(): 

3000 self._annotate_remote_with_overlap() 

3001 else: 

3002 self._annotate_remote_distinct_selectables() 

3003 

3004 def _annotate_remote_secondary(self): 

3005 """annotate 'remote' in primaryjoin, secondaryjoin 

3006 when 'secondary' is present. 

3007 

3008 """ 

3009 

3010 def repl(element): 

3011 if self.secondary.c.contains_column(element): 

3012 return element._annotate({"remote": True}) 

3013 

3014 self.primaryjoin = visitors.replacement_traverse( 

3015 self.primaryjoin, {}, repl 

3016 ) 

3017 self.secondaryjoin = visitors.replacement_traverse( 

3018 self.secondaryjoin, {}, repl 

3019 ) 

3020 

3021 def _annotate_selfref(self, fn, remote_side_given): 

3022 """annotate 'remote' in primaryjoin, secondaryjoin 

3023 when the relationship is detected as self-referential. 

3024 

3025 """ 

3026 

3027 def visit_binary(binary): 

3028 equated = binary.left.compare(binary.right) 

3029 if isinstance(binary.left, expression.ColumnClause) and isinstance( 

3030 binary.right, expression.ColumnClause 

3031 ): 

3032 # assume one to many - FKs are "remote" 

3033 if fn(binary.left): 

3034 binary.left = binary.left._annotate({"remote": True}) 

3035 if fn(binary.right) and not equated: 

3036 binary.right = binary.right._annotate({"remote": True}) 

3037 elif not remote_side_given: 

3038 self._warn_non_column_elements() 

3039 

3040 self.primaryjoin = visitors.cloned_traverse( 

3041 self.primaryjoin, {}, {"binary": visit_binary} 

3042 ) 

3043 

3044 def _annotate_remote_from_args(self): 

3045 """annotate 'remote' in primaryjoin, secondaryjoin 

3046 when the 'remote_side' or '_local_remote_pairs' 

3047 arguments are used. 

3048 

3049 """ 

3050 if self._local_remote_pairs: 

3051 if self._remote_side: 

3052 raise sa_exc.ArgumentError( 

3053 "remote_side argument is redundant " 

3054 "against more detailed _local_remote_side " 

3055 "argument." 

3056 ) 

3057 

3058 remote_side = [r for (l, r) in self._local_remote_pairs] 

3059 else: 

3060 remote_side = self._remote_side 

3061 

3062 if self._refers_to_parent_table(): 

3063 self._annotate_selfref(lambda col: col in remote_side, True) 

3064 else: 

3065 

3066 def repl(element): 

3067 # use set() to avoid generating ``__eq__()`` expressions 

3068 # against each element 

3069 if element in set(remote_side): 

3070 return element._annotate({"remote": True}) 

3071 

3072 self.primaryjoin = visitors.replacement_traverse( 

3073 self.primaryjoin, {}, repl 

3074 ) 

3075 

3076 def _annotate_remote_with_overlap(self): 

3077 """annotate 'remote' in primaryjoin, secondaryjoin 

3078 when the parent/child tables have some set of 

3079 tables in common, though is not a fully self-referential 

3080 relationship. 

3081 

3082 """ 

3083 

3084 def visit_binary(binary): 

3085 binary.left, binary.right = proc_left_right( 

3086 binary.left, binary.right 

3087 ) 

3088 binary.right, binary.left = proc_left_right( 

3089 binary.right, binary.left 

3090 ) 

3091 

3092 check_entities = ( 

3093 self.prop is not None and self.prop.mapper is not self.prop.parent 

3094 ) 

3095 

3096 def proc_left_right(left, right): 

3097 if isinstance(left, expression.ColumnClause) and isinstance( 

3098 right, expression.ColumnClause 

3099 ): 

3100 if self.child_persist_selectable.c.contains_column( 

3101 right 

3102 ) and self.parent_persist_selectable.c.contains_column(left): 

3103 right = right._annotate({"remote": True}) 

3104 elif ( 

3105 check_entities 

3106 and right._annotations.get("parentmapper") is self.prop.mapper 

3107 ): 

3108 right = right._annotate({"remote": True}) 

3109 elif ( 

3110 check_entities 

3111 and left._annotations.get("parentmapper") is self.prop.mapper 

3112 ): 

3113 left = left._annotate({"remote": True}) 

3114 else: 

3115 self._warn_non_column_elements() 

3116 

3117 return left, right 

3118 

3119 self.primaryjoin = visitors.cloned_traverse( 

3120 self.primaryjoin, {}, {"binary": visit_binary} 

3121 ) 

3122 

3123 def _annotate_remote_distinct_selectables(self): 

3124 """annotate 'remote' in primaryjoin, secondaryjoin 

3125 when the parent/child tables are entirely 

3126 separate. 

3127 

3128 """ 

3129 

3130 def repl(element): 

3131 if self.child_persist_selectable.c.contains_column(element) and ( 

3132 not self.parent_local_selectable.c.contains_column(element) 

3133 or self.child_local_selectable.c.contains_column(element) 

3134 ): 

3135 return element._annotate({"remote": True}) 

3136 

3137 self.primaryjoin = visitors.replacement_traverse( 

3138 self.primaryjoin, {}, repl 

3139 ) 

3140 

3141 def _warn_non_column_elements(self): 

3142 util.warn( 

3143 "Non-simple column elements in primary " 

3144 "join condition for property %s - consider using " 

3145 "remote() annotations to mark the remote side." % self.prop 

3146 ) 

3147 

3148 def _annotate_local(self): 

3149 """Annotate the primaryjoin and secondaryjoin 

3150 structures with 'local' annotations. 

3151 

3152 This annotates all column elements found 

3153 simultaneously in the parent table 

3154 and the join condition that don't have a 

3155 'remote' annotation set up from 

3156 _annotate_remote() or user-defined. 

3157 

3158 """ 

3159 if self._has_annotation(self.primaryjoin, "local"): 

3160 return 

3161 

3162 if self._local_remote_pairs: 

3163 local_side = util.column_set( 

3164 [l for (l, r) in self._local_remote_pairs] 

3165 ) 

3166 else: 

3167 local_side = util.column_set(self.parent_persist_selectable.c) 

3168 

3169 def locals_(elem): 

3170 if "remote" not in elem._annotations and elem in local_side: 

3171 return elem._annotate({"local": True}) 

3172 

3173 self.primaryjoin = visitors.replacement_traverse( 

3174 self.primaryjoin, {}, locals_ 

3175 ) 

3176 

3177 def _annotate_parentmapper(self): 

3178 if self.prop is None: 

3179 return 

3180 

3181 def parentmappers_(elem): 

3182 if "remote" in elem._annotations: 

3183 return elem._annotate({"parentmapper": self.prop.mapper}) 

3184 elif "local" in elem._annotations: 

3185 return elem._annotate({"parentmapper": self.prop.parent}) 

3186 

3187 self.primaryjoin = visitors.replacement_traverse( 

3188 self.primaryjoin, {}, parentmappers_ 

3189 ) 

3190 

3191 def _check_remote_side(self): 

3192 if not self.local_remote_pairs: 

3193 raise sa_exc.ArgumentError( 

3194 "Relationship %s could " 

3195 "not determine any unambiguous local/remote column " 

3196 "pairs based on join condition and remote_side " 

3197 "arguments. " 

3198 "Consider using the remote() annotation to " 

3199 "accurately mark those elements of the join " 

3200 "condition that are on the remote side of " 

3201 "the relationship." % (self.prop,) 

3202 ) 

3203 else: 

3204 

3205 not_target = util.column_set( 

3206 self.parent_persist_selectable.c 

3207 ).difference(self.child_persist_selectable.c) 

3208 

3209 for _, rmt in self.local_remote_pairs: 

3210 if rmt in not_target: 

3211 util.warn( 

3212 "Expression %s is marked as 'remote', but these " 

3213 "column(s) are local to the local side. The " 

3214 "remote() annotation is needed only for a " 

3215 "self-referential relationship where both sides " 

3216 "of the relationship refer to the same tables." 

3217 % (rmt,) 

3218 ) 

3219 

3220 def _check_foreign_cols(self, join_condition, primary): 

3221 """Check the foreign key columns collected and emit error 

3222 messages.""" 

3223 

3224 can_sync = False 

3225 

3226 foreign_cols = self._gather_columns_with_annotation( 

3227 join_condition, "foreign" 

3228 ) 

3229 

3230 has_foreign = bool(foreign_cols) 

3231 

3232 if primary: 

3233 can_sync = bool(self.synchronize_pairs) 

3234 else: 

3235 can_sync = bool(self.secondary_synchronize_pairs) 

3236 

3237 if ( 

3238 self.support_sync 

3239 and can_sync 

3240 or (not self.support_sync and has_foreign) 

3241 ): 

3242 return 

3243 

3244 # from here below is just determining the best error message 

3245 # to report. Check for a join condition using any operator 

3246 # (not just ==), perhaps they need to turn on "viewonly=True". 

3247 if self.support_sync and has_foreign and not can_sync: 

3248 err = ( 

3249 "Could not locate any simple equality expressions " 

3250 "involving locally mapped foreign key columns for " 

3251 "%s join condition " 

3252 "'%s' on relationship %s." 

3253 % ( 

3254 primary and "primary" or "secondary", 

3255 join_condition, 

3256 self.prop, 

3257 ) 

3258 ) 

3259 err += ( 

3260 " Ensure that referencing columns are associated " 

3261 "with a ForeignKey or ForeignKeyConstraint, or are " 

3262 "annotated in the join condition with the foreign() " 

3263 "annotation. To allow comparison operators other than " 

3264 "'==', the relationship can be marked as viewonly=True." 

3265 ) 

3266 

3267 raise sa_exc.ArgumentError(err) 

3268 else: 

3269 err = ( 

3270 "Could not locate any relevant foreign key columns " 

3271 "for %s join condition '%s' on relationship %s." 

3272 % ( 

3273 primary and "primary" or "secondary", 

3274 join_condition, 

3275 self.prop, 

3276 ) 

3277 ) 

3278 err += ( 

3279 " Ensure that referencing columns are associated " 

3280 "with a ForeignKey or ForeignKeyConstraint, or are " 

3281 "annotated in the join condition with the foreign() " 

3282 "annotation." 

3283 ) 

3284 raise sa_exc.ArgumentError(err) 

3285 

3286 def _determine_direction(self): 

3287 """Determine if this relationship is one to many, many to one, 

3288 many to many. 

3289 

3290 """ 

3291 if self.secondaryjoin is not None: 

3292 self.direction = MANYTOMANY 

3293 else: 

3294 parentcols = util.column_set(self.parent_persist_selectable.c) 

3295 targetcols = util.column_set(self.child_persist_selectable.c) 

3296 

3297 # fk collection which suggests ONETOMANY. 

3298 onetomany_fk = targetcols.intersection(self.foreign_key_columns) 

3299 

3300 # fk collection which suggests MANYTOONE. 

3301 

3302 manytoone_fk = parentcols.intersection(self.foreign_key_columns) 

3303 

3304 if onetomany_fk and manytoone_fk: 

3305 # fks on both sides. test for overlap of local/remote 

3306 # with foreign key. 

3307 # we will gather columns directly from their annotations 

3308 # without deannotating, so that we can distinguish on a column 

3309 # that refers to itself. 

3310 

3311 # 1. columns that are both remote and FK suggest 

3312 # onetomany. 

3313 onetomany_local = self._gather_columns_with_annotation( 

3314 self.primaryjoin, "remote", "foreign" 

3315 ) 

3316 

3317 # 2. columns that are FK but are not remote (e.g. local) 

3318 # suggest manytoone. 

3319 manytoone_local = set( 

3320 [ 

3321 c 

3322 for c in self._gather_columns_with_annotation( 

3323 self.primaryjoin, "foreign" 

3324 ) 

3325 if "remote" not in c._annotations 

3326 ] 

3327 ) 

3328 

3329 # 3. if both collections are present, remove columns that 

3330 # refer to themselves. This is for the case of 

3331 # and_(Me.id == Me.remote_id, Me.version == Me.version) 

3332 if onetomany_local and manytoone_local: 

3333 self_equated = self.remote_columns.intersection( 

3334 self.local_columns 

3335 ) 

3336 onetomany_local = onetomany_local.difference(self_equated) 

3337 manytoone_local = manytoone_local.difference(self_equated) 

3338 

3339 # at this point, if only one or the other collection is 

3340 # present, we know the direction, otherwise it's still 

3341 # ambiguous. 

3342 

3343 if onetomany_local and not manytoone_local: 

3344 self.direction = ONETOMANY 

3345 elif manytoone_local and not onetomany_local: 

3346 self.direction = MANYTOONE 

3347 else: 

3348 raise sa_exc.ArgumentError( 

3349 "Can't determine relationship" 

3350 " direction for relationship '%s' - foreign " 

3351 "key columns within the join condition are present " 

3352 "in both the parent and the child's mapped tables. " 

3353 "Ensure that only those columns referring " 

3354 "to a parent column are marked as foreign, " 

3355 "either via the foreign() annotation or " 

3356 "via the foreign_keys argument." % self.prop 

3357 ) 

3358 elif onetomany_fk: 

3359 self.direction = ONETOMANY 

3360 elif manytoone_fk: 

3361 self.direction = MANYTOONE 

3362 else: 

3363 raise sa_exc.ArgumentError( 

3364 "Can't determine relationship " 

3365 "direction for relationship '%s' - foreign " 

3366 "key columns are present in neither the parent " 

3367 "nor the child's mapped tables" % self.prop 

3368 ) 

3369 

3370 def _deannotate_pairs(self, collection): 

3371 """provide deannotation for the various lists of 

3372 pairs, so that using them in hashes doesn't incur 

3373 high-overhead __eq__() comparisons against 

3374 original columns mapped. 

3375 

3376 """ 

3377 return [(x._deannotate(), y._deannotate()) for x, y in collection] 

3378 

3379 def _setup_pairs(self): 

3380 sync_pairs = [] 

3381 lrp = util.OrderedSet([]) 

3382 secondary_sync_pairs = [] 

3383 

3384 def go(joincond, collection): 

3385 def visit_binary(binary, left, right): 

3386 if ( 

3387 "remote" in right._annotations 

3388 and "remote" not in left._annotations 

3389 and self.can_be_synced_fn(left) 

3390 ): 

3391 lrp.add((left, right)) 

3392 elif ( 

3393 "remote" in left._annotations 

3394 and "remote" not in right._annotations 

3395 and self.can_be_synced_fn(right) 

3396 ): 

3397 lrp.add((right, left)) 

3398 if binary.operator is operators.eq and self.can_be_synced_fn( 

3399 left, right 

3400 ): 

3401 if "foreign" in right._annotations: 

3402 collection.append((left, right)) 

3403 elif "foreign" in left._annotations: 

3404 collection.append((right, left)) 

3405 

3406 visit_binary_product(visit_binary, joincond) 

3407 

3408 for joincond, collection in [ 

3409 (self.primaryjoin, sync_pairs), 

3410 (self.secondaryjoin, secondary_sync_pairs), 

3411 ]: 

3412 if joincond is None: 

3413 continue 

3414 go(joincond, collection) 

3415 

3416 self.local_remote_pairs = self._deannotate_pairs(lrp) 

3417 self.synchronize_pairs = self._deannotate_pairs(sync_pairs) 

3418 self.secondary_synchronize_pairs = self._deannotate_pairs( 

3419 secondary_sync_pairs 

3420 ) 

3421 

3422 _track_overlapping_sync_targets = weakref.WeakKeyDictionary() 

3423 

3424 def _warn_for_conflicting_sync_targets(self): 

3425 if not self.support_sync: 

3426 return 

3427 

3428 # we would like to detect if we are synchronizing any column 

3429 # pairs in conflict with another relationship that wishes to sync 

3430 # an entirely different column to the same target. This is a 

3431 # very rare edge case so we will try to minimize the memory/overhead 

3432 # impact of this check 

3433 for from_, to_ in [ 

3434 (from_, to_) for (from_, to_) in self.synchronize_pairs 

3435 ] + [ 

3436 (from_, to_) for (from_, to_) in self.secondary_synchronize_pairs 

3437 ]: 

3438 # save ourselves a ton of memory and overhead by only 

3439 # considering columns that are subject to a overlapping 

3440 # FK constraints at the core level. This condition can arise 

3441 # if multiple relationships overlap foreign() directly, but 

3442 # we're going to assume it's typically a ForeignKeyConstraint- 

3443 # level configuration that benefits from this warning. 

3444 

3445 if to_ not in self._track_overlapping_sync_targets: 

3446 self._track_overlapping_sync_targets[ 

3447 to_ 

3448 ] = weakref.WeakKeyDictionary({self.prop: from_}) 

3449 else: 

3450 other_props = [] 

3451 prop_to_from = self._track_overlapping_sync_targets[to_] 

3452 

3453 for pr, fr_ in prop_to_from.items(): 

3454 if ( 

3455 not pr.mapper._dispose_called 

3456 and pr not in self.prop._reverse_property 

3457 and pr.key not in self.prop._overlaps 

3458 and self.prop.key not in pr._overlaps 

3459 # note: the "__*" symbol is used internally by 

3460 # SQLAlchemy as a general means of suppressing the 

3461 # overlaps warning for some extension cases, however 

3462 # this is not currently 

3463 # a publicly supported symbol and may change at 

3464 # any time. 

3465 and "__*" not in self.prop._overlaps 

3466 and "__*" not in pr._overlaps 

3467 and not self.prop.parent.is_sibling(pr.parent) 

3468 and not self.prop.mapper.is_sibling(pr.mapper) 

3469 and not self.prop.parent.is_sibling(pr.mapper) 

3470 and not self.prop.mapper.is_sibling(pr.parent) 

3471 and ( 

3472 self.prop.key != pr.key 

3473 or not self.prop.parent.common_parent(pr.parent) 

3474 ) 

3475 ): 

3476 

3477 other_props.append((pr, fr_)) 

3478 

3479 if other_props: 

3480 util.warn( 

3481 "relationship '%s' will copy column %s to column %s, " 

3482 "which conflicts with relationship(s): %s. " 

3483 "If this is not the intention, consider if these " 

3484 "relationships should be linked with " 

3485 "back_populates, or if viewonly=True should be " 

3486 "applied to one or more if they are read-only. " 

3487 "For the less common case that foreign key " 

3488 "constraints are partially overlapping, the " 

3489 "orm.foreign() " 

3490 "annotation can be used to isolate the columns that " 

3491 "should be written towards. To silence this " 

3492 "warning, add the parameter 'overlaps=\"%s\"' to the " 

3493 "'%s' relationship." 

3494 % ( 

3495 self.prop, 

3496 from_, 

3497 to_, 

3498 ", ".join( 

3499 sorted( 

3500 "'%s' (copies %s to %s)" % (pr, fr_, to_) 

3501 for (pr, fr_) in other_props 

3502 ) 

3503 ), 

3504 ",".join(sorted(pr.key for pr, fr in other_props)), 

3505 self.prop, 

3506 ), 

3507 code="qzyx", 

3508 ) 

3509 self._track_overlapping_sync_targets[to_][self.prop] = from_ 

3510 

3511 @util.memoized_property 

3512 def remote_columns(self): 

3513 return self._gather_join_annotations("remote") 

3514 

3515 @util.memoized_property 

3516 def local_columns(self): 

3517 return self._gather_join_annotations("local") 

3518 

3519 @util.memoized_property 

3520 def foreign_key_columns(self): 

3521 return self._gather_join_annotations("foreign") 

3522 

3523 def _gather_join_annotations(self, annotation): 

3524 s = set( 

3525 self._gather_columns_with_annotation(self.primaryjoin, annotation) 

3526 ) 

3527 if self.secondaryjoin is not None: 

3528 s.update( 

3529 self._gather_columns_with_annotation( 

3530 self.secondaryjoin, annotation 

3531 ) 

3532 ) 

3533 return {x._deannotate() for x in s} 

3534 

3535 def _gather_columns_with_annotation(self, clause, *annotation): 

3536 annotation = set(annotation) 

3537 return set( 

3538 [ 

3539 col 

3540 for col in visitors.iterate(clause, {}) 

3541 if annotation.issubset(col._annotations) 

3542 ] 

3543 ) 

3544 

3545 def join_targets( 

3546 self, 

3547 source_selectable, 

3548 dest_selectable, 

3549 aliased, 

3550 single_crit=None, 

3551 extra_criteria=(), 

3552 ): 

3553 """Given a source and destination selectable, create a 

3554 join between them. 

3555 

3556 This takes into account aliasing the join clause 

3557 to reference the appropriate corresponding columns 

3558 in the target objects, as well as the extra child 

3559 criterion, equivalent column sets, etc. 

3560 

3561 """ 

3562 # place a barrier on the destination such that 

3563 # replacement traversals won't ever dig into it. 

3564 # its internal structure remains fixed 

3565 # regardless of context. 

3566 dest_selectable = _shallow_annotate( 

3567 dest_selectable, {"no_replacement_traverse": True} 

3568 ) 

3569 

3570 primaryjoin, secondaryjoin, secondary = ( 

3571 self.primaryjoin, 

3572 self.secondaryjoin, 

3573 self.secondary, 

3574 ) 

3575 

3576 # adjust the join condition for single table inheritance, 

3577 # in the case that the join is to a subclass 

3578 # this is analogous to the 

3579 # "_adjust_for_single_table_inheritance()" method in Query. 

3580 

3581 if single_crit is not None: 

3582 if secondaryjoin is not None: 

3583 secondaryjoin = secondaryjoin & single_crit 

3584 else: 

3585 primaryjoin = primaryjoin & single_crit 

3586 

3587 if extra_criteria: 

3588 if secondaryjoin is not None: 

3589 secondaryjoin = secondaryjoin & sql.and_(*extra_criteria) 

3590 else: 

3591 primaryjoin = primaryjoin & sql.and_(*extra_criteria) 

3592 

3593 if aliased: 

3594 if secondary is not None: 

3595 secondary = secondary._anonymous_fromclause(flat=True) 

3596 primary_aliasizer = ClauseAdapter( 

3597 secondary, exclude_fn=_ColInAnnotations("local") 

3598 ) 

3599 secondary_aliasizer = ClauseAdapter( 

3600 dest_selectable, equivalents=self.child_equivalents 

3601 ).chain(primary_aliasizer) 

3602 if source_selectable is not None: 

3603 primary_aliasizer = ClauseAdapter( 

3604 secondary, exclude_fn=_ColInAnnotations("local") 

3605 ).chain( 

3606 ClauseAdapter( 

3607 source_selectable, 

3608 equivalents=self.parent_equivalents, 

3609 ) 

3610 ) 

3611 

3612 secondaryjoin = secondary_aliasizer.traverse(secondaryjoin) 

3613 else: 

3614 primary_aliasizer = ClauseAdapter( 

3615 dest_selectable, 

3616 exclude_fn=_ColInAnnotations("local"), 

3617 equivalents=self.child_equivalents, 

3618 ) 

3619 if source_selectable is not None: 

3620 primary_aliasizer.chain( 

3621 ClauseAdapter( 

3622 source_selectable, 

3623 exclude_fn=_ColInAnnotations("remote"), 

3624 equivalents=self.parent_equivalents, 

3625 ) 

3626 ) 

3627 secondary_aliasizer = None 

3628 

3629 primaryjoin = primary_aliasizer.traverse(primaryjoin) 

3630 target_adapter = secondary_aliasizer or primary_aliasizer 

3631 target_adapter.exclude_fn = None 

3632 else: 

3633 target_adapter = None 

3634 return ( 

3635 primaryjoin, 

3636 secondaryjoin, 

3637 secondary, 

3638 target_adapter, 

3639 dest_selectable, 

3640 ) 

3641 

3642 def create_lazy_clause(self, reverse_direction=False): 

3643 binds = util.column_dict() 

3644 equated_columns = util.column_dict() 

3645 

3646 has_secondary = self.secondaryjoin is not None 

3647 

3648 if has_secondary: 

3649 lookup = collections.defaultdict(list) 

3650 for l, r in self.local_remote_pairs: 

3651 lookup[l].append((l, r)) 

3652 equated_columns[r] = l 

3653 elif not reverse_direction: 

3654 for l, r in self.local_remote_pairs: 

3655 equated_columns[r] = l 

3656 else: 

3657 for l, r in self.local_remote_pairs: 

3658 equated_columns[l] = r 

3659 

3660 def col_to_bind(col): 

3661 

3662 if ( 

3663 (not reverse_direction and "local" in col._annotations) 

3664 or reverse_direction 

3665 and ( 

3666 (has_secondary and col in lookup) 

3667 or (not has_secondary and "remote" in col._annotations) 

3668 ) 

3669 ): 

3670 if col not in binds: 

3671 binds[col] = sql.bindparam( 

3672 None, None, type_=col.type, unique=True 

3673 ) 

3674 return binds[col] 

3675 return None 

3676 

3677 lazywhere = self.primaryjoin 

3678 if self.secondaryjoin is None or not reverse_direction: 

3679 lazywhere = visitors.replacement_traverse( 

3680 lazywhere, {}, col_to_bind 

3681 ) 

3682 

3683 if self.secondaryjoin is not None: 

3684 secondaryjoin = self.secondaryjoin 

3685 if reverse_direction: 

3686 secondaryjoin = visitors.replacement_traverse( 

3687 secondaryjoin, {}, col_to_bind 

3688 ) 

3689 lazywhere = sql.and_(lazywhere, secondaryjoin) 

3690 

3691 bind_to_col = {binds[col].key: col for col in binds} 

3692 

3693 return lazywhere, bind_to_col, equated_columns 

3694 

3695 

3696class _ColInAnnotations(object): 

3697 """Serializable object that tests for a name in c._annotations.""" 

3698 

3699 __slots__ = ("name",) 

3700 

3701 def __init__(self, name): 

3702 self.name = name 

3703 

3704 def __call__(self, c): 

3705 return self.name in c._annotations