Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/orm/dependency.py : 33%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# orm/dependency.py
2# Copyright (C) 2005-2020 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: http://www.opensource.org/licenses/mit-license.php
8"""Relationship dependencies.
10"""
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
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
46 self.key = prop.key
47 if not self.prop.synchronize_pairs:
48 raise sa_exc.ArgumentError(
49 "Can't build a DependencyProcessor for relationship %s. "
50 "No target attributes to populate between parent and "
51 "child are present" % self.prop
52 )
54 @classmethod
55 def from_relationship(cls, prop):
56 return _direction_to_processor[prop.direction](prop)
58 def hasparent(self, state):
59 """return True if the given object instance has a parent,
60 according to the ``InstrumentedAttribute`` handled by this
61 ``DependencyProcessor``.
63 """
64 return self.parent.class_manager.get_impl(self.key).hasparent(state)
66 def per_property_preprocessors(self, uow):
67 """establish actions and dependencies related to a flush.
69 These actions will operate on all relevant states in
70 the aggregate.
72 """
73 uow.register_preprocessor(self, True)
75 def per_property_flush_actions(self, uow):
76 after_save = unitofwork.ProcessAll(uow, self, False, True)
77 before_delete = unitofwork.ProcessAll(uow, self, True, True)
79 parent_saves = unitofwork.SaveUpdateAll(
80 uow, self.parent.primary_base_mapper
81 )
82 child_saves = unitofwork.SaveUpdateAll(
83 uow, self.mapper.primary_base_mapper
84 )
86 parent_deletes = unitofwork.DeleteAll(
87 uow, self.parent.primary_base_mapper
88 )
89 child_deletes = unitofwork.DeleteAll(
90 uow, self.mapper.primary_base_mapper
91 )
93 self.per_property_dependencies(
94 uow,
95 parent_saves,
96 child_saves,
97 parent_deletes,
98 child_deletes,
99 after_save,
100 before_delete,
101 )
103 def per_state_flush_actions(self, uow, states, isdelete):
104 """establish actions and dependencies related to a flush.
106 These actions will operate on all relevant states
107 individually. This occurs only if there are cycles
108 in the 'aggregated' version of events.
110 """
112 child_base_mapper = self.mapper.primary_base_mapper
113 child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper)
114 child_deletes = unitofwork.DeleteAll(uow, child_base_mapper)
116 # locate and disable the aggregate processors
117 # for this dependency
119 if isdelete:
120 before_delete = unitofwork.ProcessAll(uow, self, True, True)
121 before_delete.disabled = True
122 else:
123 after_save = unitofwork.ProcessAll(uow, self, False, True)
124 after_save.disabled = True
126 # check if the "child" side is part of the cycle
128 if child_saves not in uow.cycles:
129 # based on the current dependencies we use, the saves/
130 # deletes should always be in the 'cycles' collection
131 # together. if this changes, we will have to break up
132 # this method a bit more.
133 assert child_deletes not in uow.cycles
135 # child side is not part of the cycle, so we will link per-state
136 # actions to the aggregate "saves", "deletes" actions
137 child_actions = [(child_saves, False), (child_deletes, True)]
138 child_in_cycles = False
139 else:
140 child_in_cycles = True
142 # check if the "parent" side is part of the cycle
143 if not isdelete:
144 parent_saves = unitofwork.SaveUpdateAll(
145 uow, self.parent.base_mapper
146 )
147 parent_deletes = before_delete = None
148 if parent_saves in uow.cycles:
149 parent_in_cycles = True
150 else:
151 parent_deletes = unitofwork.DeleteAll(uow, self.parent.base_mapper)
152 parent_saves = after_save = None
153 if parent_deletes in uow.cycles:
154 parent_in_cycles = True
156 # now create actions /dependencies for each state.
158 for state in states:
159 # detect if there's anything changed or loaded
160 # by a preprocessor on this state/attribute. In the
161 # case of deletes we may try to load missing items here as well.
162 sum_ = state.manager[self.key].impl.get_all_pending(
163 state,
164 state.dict,
165 self._passive_delete_flag
166 if isdelete
167 else attributes.PASSIVE_NO_INITIALIZE,
168 )
170 if not sum_:
171 continue
173 if isdelete:
174 before_delete = unitofwork.ProcessState(uow, self, True, state)
175 if parent_in_cycles:
176 parent_deletes = unitofwork.DeleteState(uow, state)
177 else:
178 after_save = unitofwork.ProcessState(uow, self, False, state)
179 if parent_in_cycles:
180 parent_saves = unitofwork.SaveUpdateState(uow, state)
182 if child_in_cycles:
183 child_actions = []
184 for child_state, child in sum_:
185 if child_state not in uow.states:
186 child_action = (None, None)
187 else:
188 (deleted, listonly) = uow.states[child_state]
189 if deleted:
190 child_action = (
191 unitofwork.DeleteState(uow, child_state),
192 True,
193 )
194 else:
195 child_action = (
196 unitofwork.SaveUpdateState(uow, child_state),
197 False,
198 )
199 child_actions.append(child_action)
201 # establish dependencies between our possibly per-state
202 # parent action and our possibly per-state child action.
203 for child_action, childisdelete in child_actions:
204 self.per_state_dependencies(
205 uow,
206 parent_saves,
207 parent_deletes,
208 child_action,
209 after_save,
210 before_delete,
211 isdelete,
212 childisdelete,
213 )
215 def presort_deletes(self, uowcommit, states):
216 return False
218 def presort_saves(self, uowcommit, states):
219 return False
221 def process_deletes(self, uowcommit, states):
222 pass
224 def process_saves(self, uowcommit, states):
225 pass
227 def prop_has_changes(self, uowcommit, states, isdelete):
228 if not isdelete or self.passive_deletes:
229 passive = attributes.PASSIVE_NO_INITIALIZE
230 elif self.direction is MANYTOONE:
231 passive = attributes.PASSIVE_NO_FETCH_RELATED
232 else:
233 passive = attributes.PASSIVE_OFF
235 for s in states:
236 # TODO: add a high speed method
237 # to InstanceState which returns: attribute
238 # has a non-None value, or had one
239 history = uowcommit.get_attribute_history(s, self.key, passive)
240 if history and not history.empty():
241 return True
242 else:
243 return (
244 states
245 and not self.prop._is_self_referential
246 and self.mapper in uowcommit.mappers
247 )
249 def _verify_canload(self, state):
250 if self.prop.uselist and state is None:
251 raise exc.FlushError(
252 "Can't flush None value found in "
253 "collection %s" % (self.prop,)
254 )
255 elif state is not None and not self.mapper._canload(
256 state, allow_subtypes=not self.enable_typechecks
257 ):
258 if self.mapper._canload(state, allow_subtypes=True):
259 raise exc.FlushError(
260 "Attempting to flush an item of type "
261 "%(x)s as a member of collection "
262 '"%(y)s". Expected an object of type '
263 "%(z)s or a polymorphic subclass of "
264 "this type. If %(x)s is a subclass of "
265 '%(z)s, configure mapper "%(zm)s" to '
266 "load this subtype polymorphically, or "
267 "set enable_typechecks=False to allow "
268 "any subtype to be accepted for flush. "
269 % {
270 "x": state.class_,
271 "y": self.prop,
272 "z": self.mapper.class_,
273 "zm": self.mapper,
274 }
275 )
276 else:
277 raise exc.FlushError(
278 "Attempting to flush an item of type "
279 "%(x)s as a member of collection "
280 '"%(y)s". Expected an object of type '
281 "%(z)s or a polymorphic subclass of "
282 "this type."
283 % {
284 "x": state.class_,
285 "y": self.prop,
286 "z": self.mapper.class_,
287 }
288 )
290 def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
291 raise NotImplementedError()
293 def _get_reversed_processed_set(self, uow):
294 if not self.prop._reverse_property:
295 return None
297 process_key = tuple(
298 sorted([self.key] + [p.key for p in self.prop._reverse_property])
299 )
300 return uow.memo(("reverse_key", process_key), set)
302 def _post_update(self, state, uowcommit, related, is_m2o_delete=False):
303 for x in related:
304 if not is_m2o_delete or x is not None:
305 uowcommit.register_post_update(
306 state, [r for l, r in self.prop.synchronize_pairs]
307 )
308 break
310 def _pks_changed(self, uowcommit, state):
311 raise NotImplementedError()
313 def __repr__(self):
314 return "%s(%s)" % (self.__class__.__name__, self.prop)
317class OneToManyDP(DependencyProcessor):
318 def per_property_dependencies(
319 self,
320 uow,
321 parent_saves,
322 child_saves,
323 parent_deletes,
324 child_deletes,
325 after_save,
326 before_delete,
327 ):
328 if self.post_update:
329 child_post_updates = unitofwork.PostUpdateAll(
330 uow, self.mapper.primary_base_mapper, False
331 )
332 child_pre_updates = unitofwork.PostUpdateAll(
333 uow, self.mapper.primary_base_mapper, True
334 )
336 uow.dependencies.update(
337 [
338 (child_saves, after_save),
339 (parent_saves, after_save),
340 (after_save, child_post_updates),
341 (before_delete, child_pre_updates),
342 (child_pre_updates, parent_deletes),
343 (child_pre_updates, child_deletes),
344 ]
345 )
346 else:
347 uow.dependencies.update(
348 [
349 (parent_saves, after_save),
350 (after_save, child_saves),
351 (after_save, child_deletes),
352 (child_saves, parent_deletes),
353 (child_deletes, parent_deletes),
354 (before_delete, child_saves),
355 (before_delete, child_deletes),
356 ]
357 )
359 def per_state_dependencies(
360 self,
361 uow,
362 save_parent,
363 delete_parent,
364 child_action,
365 after_save,
366 before_delete,
367 isdelete,
368 childisdelete,
369 ):
371 if self.post_update:
373 child_post_updates = unitofwork.PostUpdateAll(
374 uow, self.mapper.primary_base_mapper, False
375 )
376 child_pre_updates = unitofwork.PostUpdateAll(
377 uow, self.mapper.primary_base_mapper, True
378 )
380 # TODO: this whole block is not covered
381 # by any tests
382 if not isdelete:
383 if childisdelete:
384 uow.dependencies.update(
385 [
386 (child_action, after_save),
387 (after_save, child_post_updates),
388 ]
389 )
390 else:
391 uow.dependencies.update(
392 [
393 (save_parent, after_save),
394 (child_action, after_save),
395 (after_save, child_post_updates),
396 ]
397 )
398 else:
399 if childisdelete:
400 uow.dependencies.update(
401 [
402 (before_delete, child_pre_updates),
403 (child_pre_updates, delete_parent),
404 ]
405 )
406 else:
407 uow.dependencies.update(
408 [
409 (before_delete, child_pre_updates),
410 (child_pre_updates, delete_parent),
411 ]
412 )
413 elif not isdelete:
414 uow.dependencies.update(
415 [
416 (save_parent, after_save),
417 (after_save, child_action),
418 (save_parent, child_action),
419 ]
420 )
421 else:
422 uow.dependencies.update(
423 [(before_delete, child_action), (child_action, delete_parent)]
424 )
426 def presort_deletes(self, uowcommit, states):
427 # head object is being deleted, and we manage its list of
428 # child objects the child objects have to have their
429 # foreign key to the parent set to NULL
430 should_null_fks = (
431 not self.cascade.delete and not self.passive_deletes == "all"
432 )
434 for state in states:
435 history = uowcommit.get_attribute_history(
436 state, self.key, self._passive_delete_flag
437 )
438 if history:
439 for child in history.deleted:
440 if child is not None and self.hasparent(child) is False:
441 if self.cascade.delete_orphan:
442 uowcommit.register_object(child, isdelete=True)
443 else:
444 uowcommit.register_object(child)
446 if should_null_fks:
447 for child in history.unchanged:
448 if child is not None:
449 uowcommit.register_object(
450 child, operation="delete", prop=self.prop
451 )
453 def presort_saves(self, uowcommit, states):
454 children_added = uowcommit.memo(("children_added", self), set)
456 should_null_fks = (
457 not self.cascade.delete_orphan
458 and not self.passive_deletes == "all"
459 )
461 for state in states:
462 pks_changed = self._pks_changed(uowcommit, state)
464 if not pks_changed or self.passive_updates:
465 passive = attributes.PASSIVE_NO_INITIALIZE
466 else:
467 passive = attributes.PASSIVE_OFF
469 history = uowcommit.get_attribute_history(state, self.key, passive)
470 if history:
471 for child in history.added:
472 if child is not None:
473 uowcommit.register_object(
474 child,
475 cancel_delete=True,
476 operation="add",
477 prop=self.prop,
478 )
480 children_added.update(history.added)
482 for child in history.deleted:
483 if not self.cascade.delete_orphan:
484 if should_null_fks:
485 uowcommit.register_object(
486 child,
487 isdelete=False,
488 operation="delete",
489 prop=self.prop,
490 )
491 elif self.hasparent(child) is False:
492 uowcommit.register_object(
493 child,
494 isdelete=True,
495 operation="delete",
496 prop=self.prop,
497 )
498 for c, m, st_, dct_ in self.mapper.cascade_iterator(
499 "delete", child
500 ):
501 uowcommit.register_object(st_, isdelete=True)
503 if pks_changed:
504 if history:
505 for child in history.unchanged:
506 if child is not None:
507 uowcommit.register_object(
508 child,
509 False,
510 self.passive_updates,
511 operation="pk change",
512 prop=self.prop,
513 )
515 def process_deletes(self, uowcommit, states):
516 # head object is being deleted, and we manage its list of
517 # child objects the child objects have to have their foreign
518 # key to the parent set to NULL this phase can be called
519 # safely for any cascade but is unnecessary if delete cascade
520 # is on.
522 if self.post_update or not self.passive_deletes == "all":
523 children_added = uowcommit.memo(("children_added", self), set)
525 for state in states:
526 history = uowcommit.get_attribute_history(
527 state, self.key, self._passive_delete_flag
528 )
529 if history:
530 for child in history.deleted:
531 if (
532 child is not None
533 and self.hasparent(child) is False
534 ):
535 self._synchronize(
536 state, child, None, True, uowcommit, False
537 )
538 if self.post_update and child:
539 self._post_update(child, uowcommit, [state])
541 if self.post_update or not self.cascade.delete:
542 for child in set(history.unchanged).difference(
543 children_added
544 ):
545 if child is not None:
546 self._synchronize(
547 state, child, None, True, uowcommit, False
548 )
549 if self.post_update and child:
550 self._post_update(
551 child, uowcommit, [state]
552 )
554 # technically, we can even remove each child from the
555 # collection here too. but this would be a somewhat
556 # inconsistent behavior since it wouldn't happen
557 # if the old parent wasn't deleted but child was moved.
559 def process_saves(self, uowcommit, states):
560 should_null_fks = (
561 not self.cascade.delete_orphan
562 and not self.passive_deletes == "all"
563 )
565 for state in states:
566 history = uowcommit.get_attribute_history(
567 state, self.key, attributes.PASSIVE_NO_INITIALIZE
568 )
569 if history:
570 for child in history.added:
571 self._synchronize(
572 state, child, None, False, uowcommit, False
573 )
574 if child is not None and self.post_update:
575 self._post_update(child, uowcommit, [state])
577 for child in history.deleted:
578 if (
579 should_null_fks
580 and not self.cascade.delete_orphan
581 and not self.hasparent(child)
582 ):
583 self._synchronize(
584 state, child, None, True, uowcommit, False
585 )
587 if self._pks_changed(uowcommit, state):
588 for child in history.unchanged:
589 self._synchronize(
590 state, child, None, False, uowcommit, True
591 )
593 def _synchronize(
594 self, state, child, associationrow, clearkeys, uowcommit, pks_changed
595 ):
596 source = state
597 dest = child
598 self._verify_canload(child)
599 if dest is None or (
600 not self.post_update and uowcommit.is_deleted(dest)
601 ):
602 return
603 if clearkeys:
604 sync.clear(dest, self.mapper, self.prop.synchronize_pairs)
605 else:
606 sync.populate(
607 source,
608 self.parent,
609 dest,
610 self.mapper,
611 self.prop.synchronize_pairs,
612 uowcommit,
613 self.passive_updates and pks_changed,
614 )
616 def _pks_changed(self, uowcommit, state):
617 return sync.source_modified(
618 uowcommit, state, self.parent, self.prop.synchronize_pairs
619 )
622class ManyToOneDP(DependencyProcessor):
623 def __init__(self, prop):
624 DependencyProcessor.__init__(self, prop)
625 for mapper in self.mapper.self_and_descendants:
626 mapper._dependency_processors.append(DetectKeySwitch(prop))
628 def per_property_dependencies(
629 self,
630 uow,
631 parent_saves,
632 child_saves,
633 parent_deletes,
634 child_deletes,
635 after_save,
636 before_delete,
637 ):
639 if self.post_update:
640 parent_post_updates = unitofwork.PostUpdateAll(
641 uow, self.parent.primary_base_mapper, False
642 )
643 parent_pre_updates = unitofwork.PostUpdateAll(
644 uow, self.parent.primary_base_mapper, True
645 )
647 uow.dependencies.update(
648 [
649 (child_saves, after_save),
650 (parent_saves, after_save),
651 (after_save, parent_post_updates),
652 (after_save, parent_pre_updates),
653 (before_delete, parent_pre_updates),
654 (parent_pre_updates, child_deletes),
655 (parent_pre_updates, parent_deletes),
656 ]
657 )
658 else:
659 uow.dependencies.update(
660 [
661 (child_saves, after_save),
662 (after_save, parent_saves),
663 (parent_saves, child_deletes),
664 (parent_deletes, child_deletes),
665 ]
666 )
668 def per_state_dependencies(
669 self,
670 uow,
671 save_parent,
672 delete_parent,
673 child_action,
674 after_save,
675 before_delete,
676 isdelete,
677 childisdelete,
678 ):
680 if self.post_update:
682 if not isdelete:
683 parent_post_updates = unitofwork.PostUpdateAll(
684 uow, self.parent.primary_base_mapper, False
685 )
686 if childisdelete:
687 uow.dependencies.update(
688 [
689 (after_save, parent_post_updates),
690 (parent_post_updates, child_action),
691 ]
692 )
693 else:
694 uow.dependencies.update(
695 [
696 (save_parent, after_save),
697 (child_action, after_save),
698 (after_save, parent_post_updates),
699 ]
700 )
701 else:
702 parent_pre_updates = unitofwork.PostUpdateAll(
703 uow, self.parent.primary_base_mapper, True
704 )
706 uow.dependencies.update(
707 [
708 (before_delete, parent_pre_updates),
709 (parent_pre_updates, delete_parent),
710 (parent_pre_updates, child_action),
711 ]
712 )
714 elif not isdelete:
715 if not childisdelete:
716 uow.dependencies.update(
717 [(child_action, after_save), (after_save, save_parent)]
718 )
719 else:
720 uow.dependencies.update([(after_save, save_parent)])
722 else:
723 if childisdelete:
724 uow.dependencies.update([(delete_parent, child_action)])
726 def presort_deletes(self, uowcommit, states):
727 if self.cascade.delete or self.cascade.delete_orphan:
728 for state in states:
729 history = uowcommit.get_attribute_history(
730 state, self.key, self._passive_delete_flag
731 )
732 if history:
733 if self.cascade.delete_orphan:
734 todelete = history.sum()
735 else:
736 todelete = history.non_deleted()
737 for child in todelete:
738 if child is None:
739 continue
740 uowcommit.register_object(
741 child,
742 isdelete=True,
743 operation="delete",
744 prop=self.prop,
745 )
746 t = self.mapper.cascade_iterator("delete", child)
747 for c, m, st_, dct_ in t:
748 uowcommit.register_object(st_, isdelete=True)
750 def presort_saves(self, uowcommit, states):
751 for state in states:
752 uowcommit.register_object(state, operation="add", prop=self.prop)
753 if self.cascade.delete_orphan:
754 history = uowcommit.get_attribute_history(
755 state, self.key, self._passive_delete_flag
756 )
757 if history:
758 for child in history.deleted:
759 if self.hasparent(child) is False:
760 uowcommit.register_object(
761 child,
762 isdelete=True,
763 operation="delete",
764 prop=self.prop,
765 )
767 t = self.mapper.cascade_iterator("delete", child)
768 for c, m, st_, dct_ in t:
769 uowcommit.register_object(st_, isdelete=True)
771 def process_deletes(self, uowcommit, states):
772 if (
773 self.post_update
774 and not self.cascade.delete_orphan
775 and not self.passive_deletes == "all"
776 ):
778 # post_update means we have to update our
779 # row to not reference the child object
780 # before we can DELETE the row
781 for state in states:
782 self._synchronize(state, None, None, True, uowcommit)
783 if state and self.post_update:
784 history = uowcommit.get_attribute_history(
785 state, self.key, self._passive_delete_flag
786 )
787 if history:
788 self._post_update(
789 state, uowcommit, history.sum(), is_m2o_delete=True
790 )
792 def process_saves(self, uowcommit, states):
793 for state in states:
794 history = uowcommit.get_attribute_history(
795 state, self.key, attributes.PASSIVE_NO_INITIALIZE
796 )
797 if history:
798 if history.added:
799 for child in history.added:
800 self._synchronize(
801 state, child, None, False, uowcommit, "add"
802 )
803 elif history.deleted:
804 self._synchronize(
805 state, None, None, True, uowcommit, "delete"
806 )
807 if self.post_update:
808 self._post_update(state, uowcommit, history.sum())
810 def _synchronize(
811 self,
812 state,
813 child,
814 associationrow,
815 clearkeys,
816 uowcommit,
817 operation=None,
818 ):
819 if state is None or (
820 not self.post_update and uowcommit.is_deleted(state)
821 ):
822 return
824 if (
825 operation is not None
826 and child is not None
827 and not uowcommit.session._contains_state(child)
828 ):
829 util.warn(
830 "Object of type %s not in session, %s "
831 "operation along '%s' won't proceed"
832 % (mapperutil.state_class_str(child), operation, self.prop)
833 )
834 return
836 if clearkeys or child is None:
837 sync.clear(state, self.parent, self.prop.synchronize_pairs)
838 else:
839 self._verify_canload(child)
840 sync.populate(
841 child,
842 self.mapper,
843 state,
844 self.parent,
845 self.prop.synchronize_pairs,
846 uowcommit,
847 False,
848 )
851class DetectKeySwitch(DependencyProcessor):
852 """For many-to-one relationships with no one-to-many backref,
853 searches for parents through the unit of work when a primary
854 key has changed and updates them.
856 Theoretically, this approach could be expanded to support transparent
857 deletion of objects referenced via many-to-one as well, although
858 the current attribute system doesn't do enough bookkeeping for this
859 to be efficient.
861 """
863 def per_property_preprocessors(self, uow):
864 if self.prop._reverse_property:
865 if self.passive_updates:
866 return
867 else:
868 if False in (
869 prop.passive_updates
870 for prop in self.prop._reverse_property
871 ):
872 return
874 uow.register_preprocessor(self, False)
876 def per_property_flush_actions(self, uow):
877 parent_saves = unitofwork.SaveUpdateAll(uow, self.parent.base_mapper)
878 after_save = unitofwork.ProcessAll(uow, self, False, False)
879 uow.dependencies.update([(parent_saves, after_save)])
881 def per_state_flush_actions(self, uow, states, isdelete):
882 pass
884 def presort_deletes(self, uowcommit, states):
885 pass
887 def presort_saves(self, uow, states):
888 if not self.passive_updates:
889 # for non-passive updates, register in the preprocess stage
890 # so that mapper save_obj() gets a hold of changes
891 self._process_key_switches(states, uow)
893 def prop_has_changes(self, uow, states, isdelete):
894 if not isdelete and self.passive_updates:
895 d = self._key_switchers(uow, states)
896 return bool(d)
898 return False
900 def process_deletes(self, uowcommit, states):
901 assert False
903 def process_saves(self, uowcommit, states):
904 # for passive updates, register objects in the process stage
905 # so that we avoid ManyToOneDP's registering the object without
906 # the listonly flag in its own preprocess stage (results in UPDATE)
907 # statements being emitted
908 assert self.passive_updates
909 self._process_key_switches(states, uowcommit)
911 def _key_switchers(self, uow, states):
912 switched, notswitched = uow.memo(
913 ("pk_switchers", self), lambda: (set(), set())
914 )
916 allstates = switched.union(notswitched)
917 for s in states:
918 if s not in allstates:
919 if self._pks_changed(uow, s):
920 switched.add(s)
921 else:
922 notswitched.add(s)
923 return switched
925 def _process_key_switches(self, deplist, uowcommit):
926 switchers = self._key_switchers(uowcommit, deplist)
927 if switchers:
928 # if primary key values have actually changed somewhere, perform
929 # a linear search through the UOW in search of a parent.
930 for state in uowcommit.session.identity_map.all_states():
931 if not issubclass(state.class_, self.parent.class_):
932 continue
933 dict_ = state.dict
934 related = state.get_impl(self.key).get(
935 state, dict_, passive=self._passive_update_flag
936 )
937 if (
938 related is not attributes.PASSIVE_NO_RESULT
939 and related is not None
940 ):
941 if self.prop.uselist:
942 if not related:
943 continue
944 related_obj = related[0]
945 else:
946 related_obj = related
947 related_state = attributes.instance_state(related_obj)
948 if related_state in switchers:
949 uowcommit.register_object(
950 state, False, self.passive_updates
951 )
952 sync.populate(
953 related_state,
954 self.mapper,
955 state,
956 self.parent,
957 self.prop.synchronize_pairs,
958 uowcommit,
959 self.passive_updates,
960 )
962 def _pks_changed(self, uowcommit, state):
963 return bool(state.key) and sync.source_modified(
964 uowcommit, state, self.mapper, self.prop.synchronize_pairs
965 )
968class ManyToManyDP(DependencyProcessor):
969 def per_property_dependencies(
970 self,
971 uow,
972 parent_saves,
973 child_saves,
974 parent_deletes,
975 child_deletes,
976 after_save,
977 before_delete,
978 ):
980 uow.dependencies.update(
981 [
982 (parent_saves, after_save),
983 (child_saves, after_save),
984 (after_save, child_deletes),
985 # a rowswitch on the parent from deleted to saved
986 # can make this one occur, as the "save" may remove
987 # an element from the
988 # "deleted" list before we have a chance to
989 # process its child rows
990 (before_delete, parent_saves),
991 (before_delete, parent_deletes),
992 (before_delete, child_deletes),
993 (before_delete, child_saves),
994 ]
995 )
997 def per_state_dependencies(
998 self,
999 uow,
1000 save_parent,
1001 delete_parent,
1002 child_action,
1003 after_save,
1004 before_delete,
1005 isdelete,
1006 childisdelete,
1007 ):
1008 if not isdelete:
1009 if childisdelete:
1010 uow.dependencies.update(
1011 [(save_parent, after_save), (after_save, child_action)]
1012 )
1013 else:
1014 uow.dependencies.update(
1015 [(save_parent, after_save), (child_action, after_save)]
1016 )
1017 else:
1018 uow.dependencies.update(
1019 [(before_delete, child_action), (before_delete, delete_parent)]
1020 )
1022 def presort_deletes(self, uowcommit, states):
1023 # TODO: no tests fail if this whole
1024 # thing is removed !!!!
1025 if not self.passive_deletes:
1026 # if no passive deletes, load history on
1027 # the collection, so that prop_has_changes()
1028 # returns True
1029 for state in states:
1030 uowcommit.get_attribute_history(
1031 state, self.key, self._passive_delete_flag
1032 )
1034 def presort_saves(self, uowcommit, states):
1035 if not self.passive_updates:
1036 # if no passive updates, load history on
1037 # each collection where parent has changed PK,
1038 # so that prop_has_changes() returns True
1039 for state in states:
1040 if self._pks_changed(uowcommit, state):
1041 history = uowcommit.get_attribute_history(
1042 state, self.key, attributes.PASSIVE_OFF
1043 )
1045 if not self.cascade.delete_orphan:
1046 return
1048 # check for child items removed from the collection
1049 # if delete_orphan check is turned on.
1050 for state in states:
1051 history = uowcommit.get_attribute_history(
1052 state, self.key, attributes.PASSIVE_NO_INITIALIZE
1053 )
1054 if history:
1055 for child in history.deleted:
1056 if self.hasparent(child) is False:
1057 uowcommit.register_object(
1058 child,
1059 isdelete=True,
1060 operation="delete",
1061 prop=self.prop,
1062 )
1063 for c, m, st_, dct_ in self.mapper.cascade_iterator(
1064 "delete", child
1065 ):
1066 uowcommit.register_object(st_, isdelete=True)
1068 def process_deletes(self, uowcommit, states):
1069 secondary_delete = []
1070 secondary_insert = []
1071 secondary_update = []
1073 processed = self._get_reversed_processed_set(uowcommit)
1074 tmp = set()
1075 for state in states:
1076 # this history should be cached already, as
1077 # we loaded it in preprocess_deletes
1078 history = uowcommit.get_attribute_history(
1079 state, self.key, self._passive_delete_flag
1080 )
1081 if history:
1082 for child in history.non_added():
1083 if child is None or (
1084 processed is not None and (state, child) in processed
1085 ):
1086 continue
1087 associationrow = {}
1088 if not self._synchronize(
1089 state,
1090 child,
1091 associationrow,
1092 False,
1093 uowcommit,
1094 "delete",
1095 ):
1096 continue
1097 secondary_delete.append(associationrow)
1099 tmp.update((c, state) for c in history.non_added())
1101 if processed is not None:
1102 processed.update(tmp)
1104 self._run_crud(
1105 uowcommit, secondary_insert, secondary_update, secondary_delete
1106 )
1108 def process_saves(self, uowcommit, states):
1109 secondary_delete = []
1110 secondary_insert = []
1111 secondary_update = []
1113 processed = self._get_reversed_processed_set(uowcommit)
1114 tmp = set()
1116 for state in states:
1117 need_cascade_pks = not self.passive_updates and self._pks_changed(
1118 uowcommit, state
1119 )
1120 if need_cascade_pks:
1121 passive = attributes.PASSIVE_OFF
1122 else:
1123 passive = attributes.PASSIVE_NO_INITIALIZE
1124 history = uowcommit.get_attribute_history(state, self.key, passive)
1125 if history:
1126 for child in history.added:
1127 if processed is not None and (state, child) in processed:
1128 continue
1129 associationrow = {}
1130 if not self._synchronize(
1131 state, child, associationrow, False, uowcommit, "add"
1132 ):
1133 continue
1134 secondary_insert.append(associationrow)
1135 for child in history.deleted:
1136 if processed is not None and (state, child) in processed:
1137 continue
1138 associationrow = {}
1139 if not self._synchronize(
1140 state,
1141 child,
1142 associationrow,
1143 False,
1144 uowcommit,
1145 "delete",
1146 ):
1147 continue
1148 secondary_delete.append(associationrow)
1150 tmp.update((c, state) for c in history.added + history.deleted)
1152 if need_cascade_pks:
1154 for child in history.unchanged:
1155 associationrow = {}
1156 sync.update(
1157 state,
1158 self.parent,
1159 associationrow,
1160 "old_",
1161 self.prop.synchronize_pairs,
1162 )
1163 sync.update(
1164 child,
1165 self.mapper,
1166 associationrow,
1167 "old_",
1168 self.prop.secondary_synchronize_pairs,
1169 )
1171 secondary_update.append(associationrow)
1173 if processed is not None:
1174 processed.update(tmp)
1176 self._run_crud(
1177 uowcommit, secondary_insert, secondary_update, secondary_delete
1178 )
1180 def _run_crud(
1181 self, uowcommit, secondary_insert, secondary_update, secondary_delete
1182 ):
1183 connection = uowcommit.transaction.connection(self.mapper)
1185 if secondary_delete:
1186 associationrow = secondary_delete[0]
1187 statement = self.secondary.delete(
1188 sql.and_(
1189 *[
1190 c == sql.bindparam(c.key, type_=c.type)
1191 for c in self.secondary.c
1192 if c.key in associationrow
1193 ]
1194 )
1195 )
1196 result = connection.execute(statement, secondary_delete)
1198 if (
1199 result.supports_sane_multi_rowcount()
1200 ) and result.rowcount != len(secondary_delete):
1201 raise exc.StaleDataError(
1202 "DELETE statement on table '%s' expected to delete "
1203 "%d row(s); Only %d were matched."
1204 % (
1205 self.secondary.description,
1206 len(secondary_delete),
1207 result.rowcount,
1208 )
1209 )
1211 if secondary_update:
1212 associationrow = secondary_update[0]
1213 statement = self.secondary.update(
1214 sql.and_(
1215 *[
1216 c == sql.bindparam("old_" + c.key, type_=c.type)
1217 for c in self.secondary.c
1218 if c.key in associationrow
1219 ]
1220 )
1221 )
1222 result = connection.execute(statement, secondary_update)
1224 if (
1225 result.supports_sane_multi_rowcount()
1226 ) and result.rowcount != len(secondary_update):
1227 raise exc.StaleDataError(
1228 "UPDATE statement on table '%s' expected to update "
1229 "%d row(s); Only %d were matched."
1230 % (
1231 self.secondary.description,
1232 len(secondary_update),
1233 result.rowcount,
1234 )
1235 )
1237 if secondary_insert:
1238 statement = self.secondary.insert()
1239 connection.execute(statement, secondary_insert)
1241 def _synchronize(
1242 self, state, child, associationrow, clearkeys, uowcommit, operation
1243 ):
1245 # this checks for None if uselist=True
1246 self._verify_canload(child)
1248 # but if uselist=False we get here. If child is None,
1249 # no association row can be generated, so return.
1250 if child is None:
1251 return False
1253 if child is not None and not uowcommit.session._contains_state(child):
1254 if not child.deleted:
1255 util.warn(
1256 "Object of type %s not in session, %s "
1257 "operation along '%s' won't proceed"
1258 % (mapperutil.state_class_str(child), operation, self.prop)
1259 )
1260 return False
1262 sync.populate_dict(
1263 state, self.parent, associationrow, self.prop.synchronize_pairs
1264 )
1265 sync.populate_dict(
1266 child,
1267 self.mapper,
1268 associationrow,
1269 self.prop.secondary_synchronize_pairs,
1270 )
1272 return True
1274 def _pks_changed(self, uowcommit, state):
1275 return sync.source_modified(
1276 uowcommit, state, self.parent, self.prop.synchronize_pairs
1277 )
1280_direction_to_processor = {
1281 ONETOMANY: OneToManyDP,
1282 MANYTOONE: ManyToOneDP,
1283 MANYTOMANY: ManyToManyDP,
1284}