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
« 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
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.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 )
55 @classmethod
56 def from_relationship(cls, prop):
57 return _direction_to_processor[prop.direction](prop)
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``.
64 """
65 return self.parent.class_manager.get_impl(self.key).hasparent(state)
67 def per_property_preprocessors(self, uow):
68 """establish actions and dependencies related to a flush.
70 These actions will operate on all relevant states in
71 the aggregate.
73 """
74 uow.register_preprocessor(self, True)
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)
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 )
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 )
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 )
104 def per_state_flush_actions(self, uow, states, isdelete):
105 """establish actions and dependencies related to a flush.
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.
111 """
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)
117 # locate and disable the aggregate processors
118 # for this dependency
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
127 # check if the "child" side is part of the cycle
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
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
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
157 # now create actions /dependencies for each state.
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 )
171 if not sum_:
172 continue
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)
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)
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 )
216 def presort_deletes(self, uowcommit, states):
217 return False
219 def presort_saves(self, uowcommit, states):
220 return False
222 def process_deletes(self, uowcommit, states):
223 pass
225 def process_saves(self, uowcommit, states):
226 pass
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
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 )
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 )
296 def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
297 raise NotImplementedError()
299 def _get_reversed_processed_set(self, uow):
300 if not self.prop._reverse_property:
301 return None
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)
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
316 def _pks_changed(self, uowcommit, state):
317 raise NotImplementedError()
319 def __repr__(self):
320 return "%s(%s)" % (self.__class__.__name__, self.prop)
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 )
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 )
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 ):
377 if self.post_update:
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 )
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 )
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 )
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)
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 )
459 def presort_saves(self, uowcommit, states):
460 children_added = uowcommit.memo(("children_added", self), set)
462 should_null_fks = (
463 not self.cascade.delete_orphan
464 and not self.passive_deletes == "all"
465 )
467 for state in states:
468 pks_changed = self._pks_changed(uowcommit, state)
470 if not pks_changed or self.passive_updates:
471 passive = attributes.PASSIVE_NO_INITIALIZE
472 else:
473 passive = attributes.PASSIVE_OFF
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 )
486 children_added.update(history.added)
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)
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 )
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.
528 if self.post_update or not self.passive_deletes == "all":
529 children_added = uowcommit.memo(("children_added", self), set)
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])
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 )
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.
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 )
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])
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 )
593 if self._pks_changed(uowcommit, state):
594 for child in history.unchanged:
595 self._synchronize(
596 state, child, None, False, uowcommit, True
597 )
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 )
622 def _pks_changed(self, uowcommit, state):
623 return sync.source_modified(
624 uowcommit, state, self.parent, self.prop.synchronize_pairs
625 )
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))
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 ):
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 )
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 )
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 ):
686 if self.post_update:
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 )
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 )
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)])
728 else:
729 if childisdelete:
730 uow.dependencies.update([(delete_parent, child_action)])
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)
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 )
773 t = self.mapper.cascade_iterator("delete", child)
774 for c, m, st_, dct_ in t:
775 uowcommit.register_object(st_, isdelete=True)
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 ):
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 )
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())
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
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
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 )
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.
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.
867 """
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
880 uow.register_preprocessor(self, False)
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)])
887 def per_state_flush_actions(self, uow, states, isdelete):
888 pass
890 def presort_deletes(self, uowcommit, states):
891 pass
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)
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)
904 return False
906 def process_deletes(self, uowcommit, states):
907 assert False
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)
917 def _key_switchers(self, uow, states):
918 switched, notswitched = uow.memo(
919 ("pk_switchers", self), lambda: (set(), set())
920 )
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
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 )
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 )
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 ):
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 )
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 )
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 )
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 )
1051 if not self.cascade.delete_orphan:
1052 return
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)
1074 def process_deletes(self, uowcommit, states):
1075 secondary_delete = []
1076 secondary_insert = []
1077 secondary_update = []
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)
1105 tmp.update((c, state) for c in history.non_added())
1107 if processed is not None:
1108 processed.update(tmp)
1110 self._run_crud(
1111 uowcommit, secondary_insert, secondary_update, secondary_delete
1112 )
1114 def process_saves(self, uowcommit, states):
1115 secondary_delete = []
1116 secondary_insert = []
1117 secondary_update = []
1119 processed = self._get_reversed_processed_set(uowcommit)
1120 tmp = set()
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)
1156 tmp.update((c, state) for c in history.added + history.deleted)
1158 if need_cascade_pks:
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 )
1177 secondary_update.append(associationrow)
1179 if processed is not None:
1180 processed.update(tmp)
1182 self._run_crud(
1183 uowcommit, secondary_insert, secondary_update, secondary_delete
1184 )
1186 def _run_crud(
1187 self, uowcommit, secondary_insert, secondary_update, secondary_delete
1188 ):
1189 connection = uowcommit.transaction.connection(self.mapper)
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)
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 )
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)
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 )
1243 if secondary_insert:
1244 statement = self.secondary.insert()
1245 connection.execute(statement, secondary_insert)
1247 def _synchronize(
1248 self, state, child, associationrow, clearkeys, uowcommit, operation
1249 ):
1251 # this checks for None if uselist=True
1252 self._verify_canload(child)
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
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
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 )
1278 return True
1280 def _pks_changed(self, uowcommit, state):
1281 return sync.source_modified(
1282 uowcommit, state, self.parent, self.prop.synchronize_pairs
1283 )
1286_direction_to_processor = {
1287 ONETOMANY: OneToManyDP,
1288 MANYTOONE: ManyToOneDP,
1289 MANYTOMANY: ManyToManyDP,
1290}