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

498 statements  

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

1# orm/dependency.py 

2# Copyright (C) 2005-2022 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"""Relationship dependencies. 

9 

10""" 

11 

12from . import attributes 

13from . import exc 

14from . import sync 

15from . import unitofwork 

16from . import util as mapperutil 

17from .interfaces import MANYTOMANY 

18from .interfaces import MANYTOONE 

19from .interfaces import ONETOMANY 

20from .. import exc as sa_exc 

21from .. import sql 

22from .. import util 

23 

24 

25class DependencyProcessor(object): 

26 def __init__(self, prop): 

27 self.prop = prop 

28 self.cascade = prop.cascade 

29 self.mapper = prop.mapper 

30 self.parent = prop.parent 

31 self.secondary = prop.secondary 

32 self.direction = prop.direction 

33 self.post_update = prop.post_update 

34 self.passive_deletes = prop.passive_deletes 

35 self.passive_updates = prop.passive_updates 

36 self.enable_typechecks = prop.enable_typechecks 

37 if self.passive_deletes: 

38 self._passive_delete_flag = attributes.PASSIVE_NO_INITIALIZE 

39 else: 

40 self._passive_delete_flag = attributes.PASSIVE_OFF 

41 if self.passive_updates: 

42 self._passive_update_flag = attributes.PASSIVE_NO_INITIALIZE 

43 else: 

44 self._passive_update_flag = attributes.PASSIVE_OFF 

45 

46 self.sort_key = "%s_%s" % (self.parent._sort_key, prop.key) 

47 self.key = prop.key 

48 if not self.prop.synchronize_pairs: 

49 raise sa_exc.ArgumentError( 

50 "Can't build a DependencyProcessor for relationship %s. " 

51 "No target attributes to populate between parent and " 

52 "child are present" % self.prop 

53 ) 

54 

55 @classmethod 

56 def from_relationship(cls, prop): 

57 return _direction_to_processor[prop.direction](prop) 

58 

59 def hasparent(self, state): 

60 """return True if the given object instance has a parent, 

61 according to the ``InstrumentedAttribute`` handled by this 

62 ``DependencyProcessor``. 

63 

64 """ 

65 return self.parent.class_manager.get_impl(self.key).hasparent(state) 

66 

67 def per_property_preprocessors(self, uow): 

68 """establish actions and dependencies related to a flush. 

69 

70 These actions will operate on all relevant states in 

71 the aggregate. 

72 

73 """ 

74 uow.register_preprocessor(self, True) 

75 

76 def per_property_flush_actions(self, uow): 

77 after_save = unitofwork.ProcessAll(uow, self, False, True) 

78 before_delete = unitofwork.ProcessAll(uow, self, True, True) 

79 

80 parent_saves = unitofwork.SaveUpdateAll( 

81 uow, self.parent.primary_base_mapper 

82 ) 

83 child_saves = unitofwork.SaveUpdateAll( 

84 uow, self.mapper.primary_base_mapper 

85 ) 

86 

87 parent_deletes = unitofwork.DeleteAll( 

88 uow, self.parent.primary_base_mapper 

89 ) 

90 child_deletes = unitofwork.DeleteAll( 

91 uow, self.mapper.primary_base_mapper 

92 ) 

93 

94 self.per_property_dependencies( 

95 uow, 

96 parent_saves, 

97 child_saves, 

98 parent_deletes, 

99 child_deletes, 

100 after_save, 

101 before_delete, 

102 ) 

103 

104 def per_state_flush_actions(self, uow, states, isdelete): 

105 """establish actions and dependencies related to a flush. 

106 

107 These actions will operate on all relevant states 

108 individually. This occurs only if there are cycles 

109 in the 'aggregated' version of events. 

110 

111 """ 

112 

113 child_base_mapper = self.mapper.primary_base_mapper 

114 child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper) 

115 child_deletes = unitofwork.DeleteAll(uow, child_base_mapper) 

116 

117 # locate and disable the aggregate processors 

118 # for this dependency 

119 

120 if isdelete: 

121 before_delete = unitofwork.ProcessAll(uow, self, True, True) 

122 before_delete.disabled = True 

123 else: 

124 after_save = unitofwork.ProcessAll(uow, self, False, True) 

125 after_save.disabled = True 

126 

127 # check if the "child" side is part of the cycle 

128 

129 if child_saves not in uow.cycles: 

130 # based on the current dependencies we use, the saves/ 

131 # deletes should always be in the 'cycles' collection 

132 # together. if this changes, we will have to break up 

133 # this method a bit more. 

134 assert child_deletes not in uow.cycles 

135 

136 # child side is not part of the cycle, so we will link per-state 

137 # actions to the aggregate "saves", "deletes" actions 

138 child_actions = [(child_saves, False), (child_deletes, True)] 

139 child_in_cycles = False 

140 else: 

141 child_in_cycles = True 

142 

143 # check if the "parent" side is part of the cycle 

144 if not isdelete: 

145 parent_saves = unitofwork.SaveUpdateAll( 

146 uow, self.parent.base_mapper 

147 ) 

148 parent_deletes = before_delete = None 

149 if parent_saves in uow.cycles: 

150 parent_in_cycles = True 

151 else: 

152 parent_deletes = unitofwork.DeleteAll(uow, self.parent.base_mapper) 

153 parent_saves = after_save = None 

154 if parent_deletes in uow.cycles: 

155 parent_in_cycles = True 

156 

157 # now create actions /dependencies for each state. 

158 

159 for state in states: 

160 # detect if there's anything changed or loaded 

161 # by a preprocessor on this state/attribute. In the 

162 # case of deletes we may try to load missing items here as well. 

163 sum_ = state.manager[self.key].impl.get_all_pending( 

164 state, 

165 state.dict, 

166 self._passive_delete_flag 

167 if isdelete 

168 else attributes.PASSIVE_NO_INITIALIZE, 

169 ) 

170 

171 if not sum_: 

172 continue 

173 

174 if isdelete: 

175 before_delete = unitofwork.ProcessState(uow, self, True, state) 

176 if parent_in_cycles: 

177 parent_deletes = unitofwork.DeleteState(uow, state) 

178 else: 

179 after_save = unitofwork.ProcessState(uow, self, False, state) 

180 if parent_in_cycles: 

181 parent_saves = unitofwork.SaveUpdateState(uow, state) 

182 

183 if child_in_cycles: 

184 child_actions = [] 

185 for child_state, child in sum_: 

186 if child_state not in uow.states: 

187 child_action = (None, None) 

188 else: 

189 (deleted, listonly) = uow.states[child_state] 

190 if deleted: 

191 child_action = ( 

192 unitofwork.DeleteState(uow, child_state), 

193 True, 

194 ) 

195 else: 

196 child_action = ( 

197 unitofwork.SaveUpdateState(uow, child_state), 

198 False, 

199 ) 

200 child_actions.append(child_action) 

201 

202 # establish dependencies between our possibly per-state 

203 # parent action and our possibly per-state child action. 

204 for child_action, childisdelete in child_actions: 

205 self.per_state_dependencies( 

206 uow, 

207 parent_saves, 

208 parent_deletes, 

209 child_action, 

210 after_save, 

211 before_delete, 

212 isdelete, 

213 childisdelete, 

214 ) 

215 

216 def presort_deletes(self, uowcommit, states): 

217 return False 

218 

219 def presort_saves(self, uowcommit, states): 

220 return False 

221 

222 def process_deletes(self, uowcommit, states): 

223 pass 

224 

225 def process_saves(self, uowcommit, states): 

226 pass 

227 

228 def prop_has_changes(self, uowcommit, states, isdelete): 

229 if not isdelete or self.passive_deletes: 

230 passive = attributes.PASSIVE_NO_INITIALIZE 

231 elif self.direction is MANYTOONE: 

232 # here, we were hoping to optimize having to fetch many-to-one 

233 # for history and ignore it, if there's no further cascades 

234 # to take place. however there are too many less common conditions 

235 # that still take place and tests in test_relationships / 

236 # test_cascade etc. will still fail. 

237 passive = attributes.PASSIVE_NO_FETCH_RELATED 

238 else: 

239 passive = attributes.PASSIVE_OFF 

240 

241 for s in states: 

242 # TODO: add a high speed method 

243 # to InstanceState which returns: attribute 

244 # has a non-None value, or had one 

245 history = uowcommit.get_attribute_history(s, self.key, passive) 

246 if history and not history.empty(): 

247 return True 

248 else: 

249 return ( 

250 states 

251 and not self.prop._is_self_referential 

252 and self.mapper in uowcommit.mappers 

253 ) 

254 

255 def _verify_canload(self, state): 

256 if self.prop.uselist and state is None: 

257 raise exc.FlushError( 

258 "Can't flush None value found in " 

259 "collection %s" % (self.prop,) 

260 ) 

261 elif state is not None and not self.mapper._canload( 

262 state, allow_subtypes=not self.enable_typechecks 

263 ): 

264 if self.mapper._canload(state, allow_subtypes=True): 

265 raise exc.FlushError( 

266 "Attempting to flush an item of type " 

267 "%(x)s as a member of collection " 

268 '"%(y)s". Expected an object of type ' 

269 "%(z)s or a polymorphic subclass of " 

270 "this type. If %(x)s is a subclass of " 

271 '%(z)s, configure mapper "%(zm)s" to ' 

272 "load this subtype polymorphically, or " 

273 "set enable_typechecks=False to allow " 

274 "any subtype to be accepted for flush. " 

275 % { 

276 "x": state.class_, 

277 "y": self.prop, 

278 "z": self.mapper.class_, 

279 "zm": self.mapper, 

280 } 

281 ) 

282 else: 

283 raise exc.FlushError( 

284 "Attempting to flush an item of type " 

285 "%(x)s as a member of collection " 

286 '"%(y)s". Expected an object of type ' 

287 "%(z)s or a polymorphic subclass of " 

288 "this type." 

289 % { 

290 "x": state.class_, 

291 "y": self.prop, 

292 "z": self.mapper.class_, 

293 } 

294 ) 

295 

296 def _synchronize(self, state, child, associationrow, clearkeys, uowcommit): 

297 raise NotImplementedError() 

298 

299 def _get_reversed_processed_set(self, uow): 

300 if not self.prop._reverse_property: 

301 return None 

302 

303 process_key = tuple( 

304 sorted([self.key] + [p.key for p in self.prop._reverse_property]) 

305 ) 

306 return uow.memo(("reverse_key", process_key), set) 

307 

308 def _post_update(self, state, uowcommit, related, is_m2o_delete=False): 

309 for x in related: 

310 if not is_m2o_delete or x is not None: 

311 uowcommit.register_post_update( 

312 state, [r for l, r in self.prop.synchronize_pairs] 

313 ) 

314 break 

315 

316 def _pks_changed(self, uowcommit, state): 

317 raise NotImplementedError() 

318 

319 def __repr__(self): 

320 return "%s(%s)" % (self.__class__.__name__, self.prop) 

321 

322 

323class OneToManyDP(DependencyProcessor): 

324 def per_property_dependencies( 

325 self, 

326 uow, 

327 parent_saves, 

328 child_saves, 

329 parent_deletes, 

330 child_deletes, 

331 after_save, 

332 before_delete, 

333 ): 

334 if self.post_update: 

335 child_post_updates = unitofwork.PostUpdateAll( 

336 uow, self.mapper.primary_base_mapper, False 

337 ) 

338 child_pre_updates = unitofwork.PostUpdateAll( 

339 uow, self.mapper.primary_base_mapper, True 

340 ) 

341 

342 uow.dependencies.update( 

343 [ 

344 (child_saves, after_save), 

345 (parent_saves, after_save), 

346 (after_save, child_post_updates), 

347 (before_delete, child_pre_updates), 

348 (child_pre_updates, parent_deletes), 

349 (child_pre_updates, child_deletes), 

350 ] 

351 ) 

352 else: 

353 uow.dependencies.update( 

354 [ 

355 (parent_saves, after_save), 

356 (after_save, child_saves), 

357 (after_save, child_deletes), 

358 (child_saves, parent_deletes), 

359 (child_deletes, parent_deletes), 

360 (before_delete, child_saves), 

361 (before_delete, child_deletes), 

362 ] 

363 ) 

364 

365 def per_state_dependencies( 

366 self, 

367 uow, 

368 save_parent, 

369 delete_parent, 

370 child_action, 

371 after_save, 

372 before_delete, 

373 isdelete, 

374 childisdelete, 

375 ): 

376 

377 if self.post_update: 

378 

379 child_post_updates = unitofwork.PostUpdateAll( 

380 uow, self.mapper.primary_base_mapper, False 

381 ) 

382 child_pre_updates = unitofwork.PostUpdateAll( 

383 uow, self.mapper.primary_base_mapper, True 

384 ) 

385 

386 # TODO: this whole block is not covered 

387 # by any tests 

388 if not isdelete: 

389 if childisdelete: 

390 uow.dependencies.update( 

391 [ 

392 (child_action, after_save), 

393 (after_save, child_post_updates), 

394 ] 

395 ) 

396 else: 

397 uow.dependencies.update( 

398 [ 

399 (save_parent, after_save), 

400 (child_action, after_save), 

401 (after_save, child_post_updates), 

402 ] 

403 ) 

404 else: 

405 if childisdelete: 

406 uow.dependencies.update( 

407 [ 

408 (before_delete, child_pre_updates), 

409 (child_pre_updates, delete_parent), 

410 ] 

411 ) 

412 else: 

413 uow.dependencies.update( 

414 [ 

415 (before_delete, child_pre_updates), 

416 (child_pre_updates, delete_parent), 

417 ] 

418 ) 

419 elif not isdelete: 

420 uow.dependencies.update( 

421 [ 

422 (save_parent, after_save), 

423 (after_save, child_action), 

424 (save_parent, child_action), 

425 ] 

426 ) 

427 else: 

428 uow.dependencies.update( 

429 [(before_delete, child_action), (child_action, delete_parent)] 

430 ) 

431 

432 def presort_deletes(self, uowcommit, states): 

433 # head object is being deleted, and we manage its list of 

434 # child objects the child objects have to have their 

435 # foreign key to the parent set to NULL 

436 should_null_fks = ( 

437 not self.cascade.delete and not self.passive_deletes == "all" 

438 ) 

439 

440 for state in states: 

441 history = uowcommit.get_attribute_history( 

442 state, self.key, self._passive_delete_flag 

443 ) 

444 if history: 

445 for child in history.deleted: 

446 if child is not None and self.hasparent(child) is False: 

447 if self.cascade.delete_orphan: 

448 uowcommit.register_object(child, isdelete=True) 

449 else: 

450 uowcommit.register_object(child) 

451 

452 if should_null_fks: 

453 for child in history.unchanged: 

454 if child is not None: 

455 uowcommit.register_object( 

456 child, operation="delete", prop=self.prop 

457 ) 

458 

459 def presort_saves(self, uowcommit, states): 

460 children_added = uowcommit.memo(("children_added", self), set) 

461 

462 should_null_fks = ( 

463 not self.cascade.delete_orphan 

464 and not self.passive_deletes == "all" 

465 ) 

466 

467 for state in states: 

468 pks_changed = self._pks_changed(uowcommit, state) 

469 

470 if not pks_changed or self.passive_updates: 

471 passive = attributes.PASSIVE_NO_INITIALIZE 

472 else: 

473 passive = attributes.PASSIVE_OFF 

474 

475 history = uowcommit.get_attribute_history(state, self.key, passive) 

476 if history: 

477 for child in history.added: 

478 if child is not None: 

479 uowcommit.register_object( 

480 child, 

481 cancel_delete=True, 

482 operation="add", 

483 prop=self.prop, 

484 ) 

485 

486 children_added.update(history.added) 

487 

488 for child in history.deleted: 

489 if not self.cascade.delete_orphan: 

490 if should_null_fks: 

491 uowcommit.register_object( 

492 child, 

493 isdelete=False, 

494 operation="delete", 

495 prop=self.prop, 

496 ) 

497 elif self.hasparent(child) is False: 

498 uowcommit.register_object( 

499 child, 

500 isdelete=True, 

501 operation="delete", 

502 prop=self.prop, 

503 ) 

504 for c, m, st_, dct_ in self.mapper.cascade_iterator( 

505 "delete", child 

506 ): 

507 uowcommit.register_object(st_, isdelete=True) 

508 

509 if pks_changed: 

510 if history: 

511 for child in history.unchanged: 

512 if child is not None: 

513 uowcommit.register_object( 

514 child, 

515 False, 

516 self.passive_updates, 

517 operation="pk change", 

518 prop=self.prop, 

519 ) 

520 

521 def process_deletes(self, uowcommit, states): 

522 # head object is being deleted, and we manage its list of 

523 # child objects the child objects have to have their foreign 

524 # key to the parent set to NULL this phase can be called 

525 # safely for any cascade but is unnecessary if delete cascade 

526 # is on. 

527 

528 if self.post_update or not self.passive_deletes == "all": 

529 children_added = uowcommit.memo(("children_added", self), set) 

530 

531 for state in states: 

532 history = uowcommit.get_attribute_history( 

533 state, self.key, self._passive_delete_flag 

534 ) 

535 if history: 

536 for child in history.deleted: 

537 if ( 

538 child is not None 

539 and self.hasparent(child) is False 

540 ): 

541 self._synchronize( 

542 state, child, None, True, uowcommit, False 

543 ) 

544 if self.post_update and child: 

545 self._post_update(child, uowcommit, [state]) 

546 

547 if self.post_update or not self.cascade.delete: 

548 for child in set(history.unchanged).difference( 

549 children_added 

550 ): 

551 if child is not None: 

552 self._synchronize( 

553 state, child, None, True, uowcommit, False 

554 ) 

555 if self.post_update and child: 

556 self._post_update( 

557 child, uowcommit, [state] 

558 ) 

559 

560 # technically, we can even remove each child from the 

561 # collection here too. but this would be a somewhat 

562 # inconsistent behavior since it wouldn't happen 

563 # if the old parent wasn't deleted but child was moved. 

564 

565 def process_saves(self, uowcommit, states): 

566 should_null_fks = ( 

567 not self.cascade.delete_orphan 

568 and not self.passive_deletes == "all" 

569 ) 

570 

571 for state in states: 

572 history = uowcommit.get_attribute_history( 

573 state, self.key, attributes.PASSIVE_NO_INITIALIZE 

574 ) 

575 if history: 

576 for child in history.added: 

577 self._synchronize( 

578 state, child, None, False, uowcommit, False 

579 ) 

580 if child is not None and self.post_update: 

581 self._post_update(child, uowcommit, [state]) 

582 

583 for child in history.deleted: 

584 if ( 

585 should_null_fks 

586 and not self.cascade.delete_orphan 

587 and not self.hasparent(child) 

588 ): 

589 self._synchronize( 

590 state, child, None, True, uowcommit, False 

591 ) 

592 

593 if self._pks_changed(uowcommit, state): 

594 for child in history.unchanged: 

595 self._synchronize( 

596 state, child, None, False, uowcommit, True 

597 ) 

598 

599 def _synchronize( 

600 self, state, child, associationrow, clearkeys, uowcommit, pks_changed 

601 ): 

602 source = state 

603 dest = child 

604 self._verify_canload(child) 

605 if dest is None or ( 

606 not self.post_update and uowcommit.is_deleted(dest) 

607 ): 

608 return 

609 if clearkeys: 

610 sync.clear(dest, self.mapper, self.prop.synchronize_pairs) 

611 else: 

612 sync.populate( 

613 source, 

614 self.parent, 

615 dest, 

616 self.mapper, 

617 self.prop.synchronize_pairs, 

618 uowcommit, 

619 self.passive_updates and pks_changed, 

620 ) 

621 

622 def _pks_changed(self, uowcommit, state): 

623 return sync.source_modified( 

624 uowcommit, state, self.parent, self.prop.synchronize_pairs 

625 ) 

626 

627 

628class ManyToOneDP(DependencyProcessor): 

629 def __init__(self, prop): 

630 DependencyProcessor.__init__(self, prop) 

631 for mapper in self.mapper.self_and_descendants: 

632 mapper._dependency_processors.append(DetectKeySwitch(prop)) 

633 

634 def per_property_dependencies( 

635 self, 

636 uow, 

637 parent_saves, 

638 child_saves, 

639 parent_deletes, 

640 child_deletes, 

641 after_save, 

642 before_delete, 

643 ): 

644 

645 if self.post_update: 

646 parent_post_updates = unitofwork.PostUpdateAll( 

647 uow, self.parent.primary_base_mapper, False 

648 ) 

649 parent_pre_updates = unitofwork.PostUpdateAll( 

650 uow, self.parent.primary_base_mapper, True 

651 ) 

652 

653 uow.dependencies.update( 

654 [ 

655 (child_saves, after_save), 

656 (parent_saves, after_save), 

657 (after_save, parent_post_updates), 

658 (after_save, parent_pre_updates), 

659 (before_delete, parent_pre_updates), 

660 (parent_pre_updates, child_deletes), 

661 (parent_pre_updates, parent_deletes), 

662 ] 

663 ) 

664 else: 

665 uow.dependencies.update( 

666 [ 

667 (child_saves, after_save), 

668 (after_save, parent_saves), 

669 (parent_saves, child_deletes), 

670 (parent_deletes, child_deletes), 

671 ] 

672 ) 

673 

674 def per_state_dependencies( 

675 self, 

676 uow, 

677 save_parent, 

678 delete_parent, 

679 child_action, 

680 after_save, 

681 before_delete, 

682 isdelete, 

683 childisdelete, 

684 ): 

685 

686 if self.post_update: 

687 

688 if not isdelete: 

689 parent_post_updates = unitofwork.PostUpdateAll( 

690 uow, self.parent.primary_base_mapper, False 

691 ) 

692 if childisdelete: 

693 uow.dependencies.update( 

694 [ 

695 (after_save, parent_post_updates), 

696 (parent_post_updates, child_action), 

697 ] 

698 ) 

699 else: 

700 uow.dependencies.update( 

701 [ 

702 (save_parent, after_save), 

703 (child_action, after_save), 

704 (after_save, parent_post_updates), 

705 ] 

706 ) 

707 else: 

708 parent_pre_updates = unitofwork.PostUpdateAll( 

709 uow, self.parent.primary_base_mapper, True 

710 ) 

711 

712 uow.dependencies.update( 

713 [ 

714 (before_delete, parent_pre_updates), 

715 (parent_pre_updates, delete_parent), 

716 (parent_pre_updates, child_action), 

717 ] 

718 ) 

719 

720 elif not isdelete: 

721 if not childisdelete: 

722 uow.dependencies.update( 

723 [(child_action, after_save), (after_save, save_parent)] 

724 ) 

725 else: 

726 uow.dependencies.update([(after_save, save_parent)]) 

727 

728 else: 

729 if childisdelete: 

730 uow.dependencies.update([(delete_parent, child_action)]) 

731 

732 def presort_deletes(self, uowcommit, states): 

733 if self.cascade.delete or self.cascade.delete_orphan: 

734 for state in states: 

735 history = uowcommit.get_attribute_history( 

736 state, self.key, self._passive_delete_flag 

737 ) 

738 if history: 

739 if self.cascade.delete_orphan: 

740 todelete = history.sum() 

741 else: 

742 todelete = history.non_deleted() 

743 for child in todelete: 

744 if child is None: 

745 continue 

746 uowcommit.register_object( 

747 child, 

748 isdelete=True, 

749 operation="delete", 

750 prop=self.prop, 

751 ) 

752 t = self.mapper.cascade_iterator("delete", child) 

753 for c, m, st_, dct_ in t: 

754 uowcommit.register_object(st_, isdelete=True) 

755 

756 def presort_saves(self, uowcommit, states): 

757 for state in states: 

758 uowcommit.register_object(state, operation="add", prop=self.prop) 

759 if self.cascade.delete_orphan: 

760 history = uowcommit.get_attribute_history( 

761 state, self.key, self._passive_delete_flag 

762 ) 

763 if history: 

764 for child in history.deleted: 

765 if self.hasparent(child) is False: 

766 uowcommit.register_object( 

767 child, 

768 isdelete=True, 

769 operation="delete", 

770 prop=self.prop, 

771 ) 

772 

773 t = self.mapper.cascade_iterator("delete", child) 

774 for c, m, st_, dct_ in t: 

775 uowcommit.register_object(st_, isdelete=True) 

776 

777 def process_deletes(self, uowcommit, states): 

778 if ( 

779 self.post_update 

780 and not self.cascade.delete_orphan 

781 and not self.passive_deletes == "all" 

782 ): 

783 

784 # post_update means we have to update our 

785 # row to not reference the child object 

786 # before we can DELETE the row 

787 for state in states: 

788 self._synchronize(state, None, None, True, uowcommit) 

789 if state and self.post_update: 

790 history = uowcommit.get_attribute_history( 

791 state, self.key, self._passive_delete_flag 

792 ) 

793 if history: 

794 self._post_update( 

795 state, uowcommit, history.sum(), is_m2o_delete=True 

796 ) 

797 

798 def process_saves(self, uowcommit, states): 

799 for state in states: 

800 history = uowcommit.get_attribute_history( 

801 state, self.key, attributes.PASSIVE_NO_INITIALIZE 

802 ) 

803 if history: 

804 if history.added: 

805 for child in history.added: 

806 self._synchronize( 

807 state, child, None, False, uowcommit, "add" 

808 ) 

809 elif history.deleted: 

810 self._synchronize( 

811 state, None, None, True, uowcommit, "delete" 

812 ) 

813 if self.post_update: 

814 self._post_update(state, uowcommit, history.sum()) 

815 

816 def _synchronize( 

817 self, 

818 state, 

819 child, 

820 associationrow, 

821 clearkeys, 

822 uowcommit, 

823 operation=None, 

824 ): 

825 if state is None or ( 

826 not self.post_update and uowcommit.is_deleted(state) 

827 ): 

828 return 

829 

830 if ( 

831 operation is not None 

832 and child is not None 

833 and not uowcommit.session._contains_state(child) 

834 ): 

835 util.warn( 

836 "Object of type %s not in session, %s " 

837 "operation along '%s' won't proceed" 

838 % (mapperutil.state_class_str(child), operation, self.prop) 

839 ) 

840 return 

841 

842 if clearkeys or child is None: 

843 sync.clear(state, self.parent, self.prop.synchronize_pairs) 

844 else: 

845 self._verify_canload(child) 

846 sync.populate( 

847 child, 

848 self.mapper, 

849 state, 

850 self.parent, 

851 self.prop.synchronize_pairs, 

852 uowcommit, 

853 False, 

854 ) 

855 

856 

857class DetectKeySwitch(DependencyProcessor): 

858 """For many-to-one relationships with no one-to-many backref, 

859 searches for parents through the unit of work when a primary 

860 key has changed and updates them. 

861 

862 Theoretically, this approach could be expanded to support transparent 

863 deletion of objects referenced via many-to-one as well, although 

864 the current attribute system doesn't do enough bookkeeping for this 

865 to be efficient. 

866 

867 """ 

868 

869 def per_property_preprocessors(self, uow): 

870 if self.prop._reverse_property: 

871 if self.passive_updates: 

872 return 

873 else: 

874 if False in ( 

875 prop.passive_updates 

876 for prop in self.prop._reverse_property 

877 ): 

878 return 

879 

880 uow.register_preprocessor(self, False) 

881 

882 def per_property_flush_actions(self, uow): 

883 parent_saves = unitofwork.SaveUpdateAll(uow, self.parent.base_mapper) 

884 after_save = unitofwork.ProcessAll(uow, self, False, False) 

885 uow.dependencies.update([(parent_saves, after_save)]) 

886 

887 def per_state_flush_actions(self, uow, states, isdelete): 

888 pass 

889 

890 def presort_deletes(self, uowcommit, states): 

891 pass 

892 

893 def presort_saves(self, uow, states): 

894 if not self.passive_updates: 

895 # for non-passive updates, register in the preprocess stage 

896 # so that mapper save_obj() gets a hold of changes 

897 self._process_key_switches(states, uow) 

898 

899 def prop_has_changes(self, uow, states, isdelete): 

900 if not isdelete and self.passive_updates: 

901 d = self._key_switchers(uow, states) 

902 return bool(d) 

903 

904 return False 

905 

906 def process_deletes(self, uowcommit, states): 

907 assert False 

908 

909 def process_saves(self, uowcommit, states): 

910 # for passive updates, register objects in the process stage 

911 # so that we avoid ManyToOneDP's registering the object without 

912 # the listonly flag in its own preprocess stage (results in UPDATE) 

913 # statements being emitted 

914 assert self.passive_updates 

915 self._process_key_switches(states, uowcommit) 

916 

917 def _key_switchers(self, uow, states): 

918 switched, notswitched = uow.memo( 

919 ("pk_switchers", self), lambda: (set(), set()) 

920 ) 

921 

922 allstates = switched.union(notswitched) 

923 for s in states: 

924 if s not in allstates: 

925 if self._pks_changed(uow, s): 

926 switched.add(s) 

927 else: 

928 notswitched.add(s) 

929 return switched 

930 

931 def _process_key_switches(self, deplist, uowcommit): 

932 switchers = self._key_switchers(uowcommit, deplist) 

933 if switchers: 

934 # if primary key values have actually changed somewhere, perform 

935 # a linear search through the UOW in search of a parent. 

936 for state in uowcommit.session.identity_map.all_states(): 

937 if not issubclass(state.class_, self.parent.class_): 

938 continue 

939 dict_ = state.dict 

940 related = state.get_impl(self.key).get( 

941 state, dict_, passive=self._passive_update_flag 

942 ) 

943 if ( 

944 related is not attributes.PASSIVE_NO_RESULT 

945 and related is not None 

946 ): 

947 if self.prop.uselist: 

948 if not related: 

949 continue 

950 related_obj = related[0] 

951 else: 

952 related_obj = related 

953 related_state = attributes.instance_state(related_obj) 

954 if related_state in switchers: 

955 uowcommit.register_object( 

956 state, False, self.passive_updates 

957 ) 

958 sync.populate( 

959 related_state, 

960 self.mapper, 

961 state, 

962 self.parent, 

963 self.prop.synchronize_pairs, 

964 uowcommit, 

965 self.passive_updates, 

966 ) 

967 

968 def _pks_changed(self, uowcommit, state): 

969 return bool(state.key) and sync.source_modified( 

970 uowcommit, state, self.mapper, self.prop.synchronize_pairs 

971 ) 

972 

973 

974class ManyToManyDP(DependencyProcessor): 

975 def per_property_dependencies( 

976 self, 

977 uow, 

978 parent_saves, 

979 child_saves, 

980 parent_deletes, 

981 child_deletes, 

982 after_save, 

983 before_delete, 

984 ): 

985 

986 uow.dependencies.update( 

987 [ 

988 (parent_saves, after_save), 

989 (child_saves, after_save), 

990 (after_save, child_deletes), 

991 # a rowswitch on the parent from deleted to saved 

992 # can make this one occur, as the "save" may remove 

993 # an element from the 

994 # "deleted" list before we have a chance to 

995 # process its child rows 

996 (before_delete, parent_saves), 

997 (before_delete, parent_deletes), 

998 (before_delete, child_deletes), 

999 (before_delete, child_saves), 

1000 ] 

1001 ) 

1002 

1003 def per_state_dependencies( 

1004 self, 

1005 uow, 

1006 save_parent, 

1007 delete_parent, 

1008 child_action, 

1009 after_save, 

1010 before_delete, 

1011 isdelete, 

1012 childisdelete, 

1013 ): 

1014 if not isdelete: 

1015 if childisdelete: 

1016 uow.dependencies.update( 

1017 [(save_parent, after_save), (after_save, child_action)] 

1018 ) 

1019 else: 

1020 uow.dependencies.update( 

1021 [(save_parent, after_save), (child_action, after_save)] 

1022 ) 

1023 else: 

1024 uow.dependencies.update( 

1025 [(before_delete, child_action), (before_delete, delete_parent)] 

1026 ) 

1027 

1028 def presort_deletes(self, uowcommit, states): 

1029 # TODO: no tests fail if this whole 

1030 # thing is removed !!!! 

1031 if not self.passive_deletes: 

1032 # if no passive deletes, load history on 

1033 # the collection, so that prop_has_changes() 

1034 # returns True 

1035 for state in states: 

1036 uowcommit.get_attribute_history( 

1037 state, self.key, self._passive_delete_flag 

1038 ) 

1039 

1040 def presort_saves(self, uowcommit, states): 

1041 if not self.passive_updates: 

1042 # if no passive updates, load history on 

1043 # each collection where parent has changed PK, 

1044 # so that prop_has_changes() returns True 

1045 for state in states: 

1046 if self._pks_changed(uowcommit, state): 

1047 history = uowcommit.get_attribute_history( 

1048 state, self.key, attributes.PASSIVE_OFF 

1049 ) 

1050 

1051 if not self.cascade.delete_orphan: 

1052 return 

1053 

1054 # check for child items removed from the collection 

1055 # if delete_orphan check is turned on. 

1056 for state in states: 

1057 history = uowcommit.get_attribute_history( 

1058 state, self.key, attributes.PASSIVE_NO_INITIALIZE 

1059 ) 

1060 if history: 

1061 for child in history.deleted: 

1062 if self.hasparent(child) is False: 

1063 uowcommit.register_object( 

1064 child, 

1065 isdelete=True, 

1066 operation="delete", 

1067 prop=self.prop, 

1068 ) 

1069 for c, m, st_, dct_ in self.mapper.cascade_iterator( 

1070 "delete", child 

1071 ): 

1072 uowcommit.register_object(st_, isdelete=True) 

1073 

1074 def process_deletes(self, uowcommit, states): 

1075 secondary_delete = [] 

1076 secondary_insert = [] 

1077 secondary_update = [] 

1078 

1079 processed = self._get_reversed_processed_set(uowcommit) 

1080 tmp = set() 

1081 for state in states: 

1082 # this history should be cached already, as 

1083 # we loaded it in preprocess_deletes 

1084 history = uowcommit.get_attribute_history( 

1085 state, self.key, self._passive_delete_flag 

1086 ) 

1087 if history: 

1088 for child in history.non_added(): 

1089 if child is None or ( 

1090 processed is not None and (state, child) in processed 

1091 ): 

1092 continue 

1093 associationrow = {} 

1094 if not self._synchronize( 

1095 state, 

1096 child, 

1097 associationrow, 

1098 False, 

1099 uowcommit, 

1100 "delete", 

1101 ): 

1102 continue 

1103 secondary_delete.append(associationrow) 

1104 

1105 tmp.update((c, state) for c in history.non_added()) 

1106 

1107 if processed is not None: 

1108 processed.update(tmp) 

1109 

1110 self._run_crud( 

1111 uowcommit, secondary_insert, secondary_update, secondary_delete 

1112 ) 

1113 

1114 def process_saves(self, uowcommit, states): 

1115 secondary_delete = [] 

1116 secondary_insert = [] 

1117 secondary_update = [] 

1118 

1119 processed = self._get_reversed_processed_set(uowcommit) 

1120 tmp = set() 

1121 

1122 for state in states: 

1123 need_cascade_pks = not self.passive_updates and self._pks_changed( 

1124 uowcommit, state 

1125 ) 

1126 if need_cascade_pks: 

1127 passive = attributes.PASSIVE_OFF 

1128 else: 

1129 passive = attributes.PASSIVE_NO_INITIALIZE 

1130 history = uowcommit.get_attribute_history(state, self.key, passive) 

1131 if history: 

1132 for child in history.added: 

1133 if processed is not None and (state, child) in processed: 

1134 continue 

1135 associationrow = {} 

1136 if not self._synchronize( 

1137 state, child, associationrow, False, uowcommit, "add" 

1138 ): 

1139 continue 

1140 secondary_insert.append(associationrow) 

1141 for child in history.deleted: 

1142 if processed is not None and (state, child) in processed: 

1143 continue 

1144 associationrow = {} 

1145 if not self._synchronize( 

1146 state, 

1147 child, 

1148 associationrow, 

1149 False, 

1150 uowcommit, 

1151 "delete", 

1152 ): 

1153 continue 

1154 secondary_delete.append(associationrow) 

1155 

1156 tmp.update((c, state) for c in history.added + history.deleted) 

1157 

1158 if need_cascade_pks: 

1159 

1160 for child in history.unchanged: 

1161 associationrow = {} 

1162 sync.update( 

1163 state, 

1164 self.parent, 

1165 associationrow, 

1166 "old_", 

1167 self.prop.synchronize_pairs, 

1168 ) 

1169 sync.update( 

1170 child, 

1171 self.mapper, 

1172 associationrow, 

1173 "old_", 

1174 self.prop.secondary_synchronize_pairs, 

1175 ) 

1176 

1177 secondary_update.append(associationrow) 

1178 

1179 if processed is not None: 

1180 processed.update(tmp) 

1181 

1182 self._run_crud( 

1183 uowcommit, secondary_insert, secondary_update, secondary_delete 

1184 ) 

1185 

1186 def _run_crud( 

1187 self, uowcommit, secondary_insert, secondary_update, secondary_delete 

1188 ): 

1189 connection = uowcommit.transaction.connection(self.mapper) 

1190 

1191 if secondary_delete: 

1192 associationrow = secondary_delete[0] 

1193 statement = self.secondary.delete().where( 

1194 sql.and_( 

1195 *[ 

1196 c == sql.bindparam(c.key, type_=c.type) 

1197 for c in self.secondary.c 

1198 if c.key in associationrow 

1199 ] 

1200 ) 

1201 ) 

1202 result = connection.execute(statement, secondary_delete) 

1203 

1204 if ( 

1205 result.supports_sane_multi_rowcount() 

1206 ) and result.rowcount != len(secondary_delete): 

1207 raise exc.StaleDataError( 

1208 "DELETE statement on table '%s' expected to delete " 

1209 "%d row(s); Only %d were matched." 

1210 % ( 

1211 self.secondary.description, 

1212 len(secondary_delete), 

1213 result.rowcount, 

1214 ) 

1215 ) 

1216 

1217 if secondary_update: 

1218 associationrow = secondary_update[0] 

1219 statement = self.secondary.update().where( 

1220 sql.and_( 

1221 *[ 

1222 c == sql.bindparam("old_" + c.key, type_=c.type) 

1223 for c in self.secondary.c 

1224 if c.key in associationrow 

1225 ] 

1226 ) 

1227 ) 

1228 result = connection.execute(statement, secondary_update) 

1229 

1230 if ( 

1231 result.supports_sane_multi_rowcount() 

1232 ) and result.rowcount != len(secondary_update): 

1233 raise exc.StaleDataError( 

1234 "UPDATE statement on table '%s' expected to update " 

1235 "%d row(s); Only %d were matched." 

1236 % ( 

1237 self.secondary.description, 

1238 len(secondary_update), 

1239 result.rowcount, 

1240 ) 

1241 ) 

1242 

1243 if secondary_insert: 

1244 statement = self.secondary.insert() 

1245 connection.execute(statement, secondary_insert) 

1246 

1247 def _synchronize( 

1248 self, state, child, associationrow, clearkeys, uowcommit, operation 

1249 ): 

1250 

1251 # this checks for None if uselist=True 

1252 self._verify_canload(child) 

1253 

1254 # but if uselist=False we get here. If child is None, 

1255 # no association row can be generated, so return. 

1256 if child is None: 

1257 return False 

1258 

1259 if child is not None and not uowcommit.session._contains_state(child): 

1260 if not child.deleted: 

1261 util.warn( 

1262 "Object of type %s not in session, %s " 

1263 "operation along '%s' won't proceed" 

1264 % (mapperutil.state_class_str(child), operation, self.prop) 

1265 ) 

1266 return False 

1267 

1268 sync.populate_dict( 

1269 state, self.parent, associationrow, self.prop.synchronize_pairs 

1270 ) 

1271 sync.populate_dict( 

1272 child, 

1273 self.mapper, 

1274 associationrow, 

1275 self.prop.secondary_synchronize_pairs, 

1276 ) 

1277 

1278 return True 

1279 

1280 def _pks_changed(self, uowcommit, state): 

1281 return sync.source_modified( 

1282 uowcommit, state, self.parent, self.prop.synchronize_pairs 

1283 ) 

1284 

1285 

1286_direction_to_processor = { 

1287 ONETOMANY: OneToManyDP, 

1288 MANYTOONE: ManyToOneDP, 

1289 MANYTOMANY: ManyToManyDP, 

1290}