opt
/
hc_python
/
lib
/
python3.12
/
site-packages
/
sqlalchemy
/
orm
/
Go to Home Directory
+
Upload
Create File
root@0UT1S:~$
Execute
By Order of Mr.0UT1S
[DIR] ..
N/A
[DIR] __pycache__
N/A
__init__.py
8.26 KB
Rename
Delete
_orm_constructors.py
101.20 KB
Rename
Delete
_typing.py
4.86 KB
Rename
Delete
attributes.py
90.37 KB
Rename
Delete
base.py
26.86 KB
Rename
Delete
bulk_persistence.py
70.96 KB
Rename
Delete
clsregistry.py
17.55 KB
Rename
Delete
collections.py
51.03 KB
Rename
Delete
context.py
112.42 KB
Rename
Delete
decl_api.py
63.45 KB
Rename
Delete
decl_base.py
81.34 KB
Rename
Delete
dependency.py
46.51 KB
Rename
Delete
descriptor_props.py
36.36 KB
Rename
Delete
dynamic.py
9.59 KB
Rename
Delete
evaluator.py
12.06 KB
Rename
Delete
events.py
124.79 KB
Rename
Delete
exc.py
7.24 KB
Rename
Delete
identity.py
9.03 KB
Rename
Delete
instrumentation.py
23.75 KB
Rename
Delete
interfaces.py
47.65 KB
Rename
Delete
loading.py
56.91 KB
Rename
Delete
mapped_collection.py
19.22 KB
Rename
Delete
mapper.py
167.63 KB
Rename
Delete
path_registry.py
25.31 KB
Rename
Delete
persistence.py
60.25 KB
Rename
Delete
properties.py
28.38 KB
Rename
Delete
query.py
115.95 KB
Rename
Delete
relationships.py
125.88 KB
Rename
Delete
scoping.py
76.77 KB
Rename
Delete
session.py
191.52 KB
Rename
Delete
state.py
36.79 KB
Rename
Delete
state_changes.py
6.66 KB
Rename
Delete
strategies.py
117.06 KB
Rename
Delete
strategy_options.py
83.05 KB
Rename
Delete
sync.py
5.64 KB
Rename
Delete
unitofwork.py
26.40 KB
Rename
Delete
util.py
78.94 KB
Rename
Delete
writeonly.py
21.78 KB
Rename
Delete
# orm/dependency.py # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors # <see AUTHORS file> # # This module is part of SQLAlchemy and is released under # the MIT License: https://www.opensource.org/licenses/mit-license.php # mypy: ignore-errors """Relationship dependencies. """ from __future__ import annotations from . import attributes from . import exc from . import sync from . import unitofwork from . import util as mapperutil from .interfaces import MANYTOMANY from .interfaces import MANYTOONE from .interfaces import ONETOMANY from .. import exc as sa_exc from .. import sql from .. import util class DependencyProcessor: def __init__(self, prop): self.prop = prop self.cascade = prop.cascade self.mapper = prop.mapper self.parent = prop.parent self.secondary = prop.secondary self.direction = prop.direction self.post_update = prop.post_update self.passive_deletes = prop.passive_deletes self.passive_updates = prop.passive_updates self.enable_typechecks = prop.enable_typechecks if self.passive_deletes: self._passive_delete_flag = attributes.PASSIVE_NO_INITIALIZE else: self._passive_delete_flag = attributes.PASSIVE_OFF if self.passive_updates: self._passive_update_flag = attributes.PASSIVE_NO_INITIALIZE else: self._passive_update_flag = attributes.PASSIVE_OFF self.sort_key = "%s_%s" % (self.parent._sort_key, prop.key) self.key = prop.key if not self.prop.synchronize_pairs: raise sa_exc.ArgumentError( "Can't build a DependencyProcessor for relationship %s. " "No target attributes to populate between parent and " "child are present" % self.prop ) @classmethod def from_relationship(cls, prop): return _direction_to_processor[prop.direction](prop) def hasparent(self, state): """return True if the given object instance has a parent, according to the ``InstrumentedAttribute`` handled by this ``DependencyProcessor``. """ return self.parent.class_manager.get_impl(self.key).hasparent(state) def per_property_preprocessors(self, uow): """establish actions and dependencies related to a flush. These actions will operate on all relevant states in the aggregate. """ uow.register_preprocessor(self, True) def per_property_flush_actions(self, uow): after_save = unitofwork.ProcessAll(uow, self, False, True) before_delete = unitofwork.ProcessAll(uow, self, True, True) parent_saves = unitofwork.SaveUpdateAll( uow, self.parent.primary_base_mapper ) child_saves = unitofwork.SaveUpdateAll( uow, self.mapper.primary_base_mapper ) parent_deletes = unitofwork.DeleteAll( uow, self.parent.primary_base_mapper ) child_deletes = unitofwork.DeleteAll( uow, self.mapper.primary_base_mapper ) self.per_property_dependencies( uow, parent_saves, child_saves, parent_deletes, child_deletes, after_save, before_delete, ) def per_state_flush_actions(self, uow, states, isdelete): """establish actions and dependencies related to a flush. These actions will operate on all relevant states individually. This occurs only if there are cycles in the 'aggregated' version of events. """ child_base_mapper = self.mapper.primary_base_mapper child_saves = unitofwork.SaveUpdateAll(uow, child_base_mapper) child_deletes = unitofwork.DeleteAll(uow, child_base_mapper) # locate and disable the aggregate processors # for this dependency if isdelete: before_delete = unitofwork.ProcessAll(uow, self, True, True) before_delete.disabled = True else: after_save = unitofwork.ProcessAll(uow, self, False, True) after_save.disabled = True # check if the "child" side is part of the cycle if child_saves not in uow.cycles: # based on the current dependencies we use, the saves/ # deletes should always be in the 'cycles' collection # together. if this changes, we will have to break up # this method a bit more. assert child_deletes not in uow.cycles # child side is not part of the cycle, so we will link per-state # actions to the aggregate "saves", "deletes" actions child_actions = [(child_saves, False), (child_deletes, True)] child_in_cycles = False else: child_in_cycles = True # check if the "parent" side is part of the cycle if not isdelete: parent_saves = unitofwork.SaveUpdateAll( uow, self.parent.base_mapper ) parent_deletes = before_delete = None if parent_saves in uow.cycles: parent_in_cycles = True else: parent_deletes = unitofwork.DeleteAll(uow, self.parent.base_mapper) parent_saves = after_save = None if parent_deletes in uow.cycles: parent_in_cycles = True # now create actions /dependencies for each state. for state in states: # detect if there's anything changed or loaded # by a preprocessor on this state/attribute. In the # case of deletes we may try to load missing items here as well. sum_ = state.manager[self.key].impl.get_all_pending( state, state.dict, ( self._passive_delete_flag if isdelete else attributes.PASSIVE_NO_INITIALIZE ), ) if not sum_: continue if isdelete: before_delete = unitofwork.ProcessState(uow, self, True, state) if parent_in_cycles: parent_deletes = unitofwork.DeleteState(uow, state) else: after_save = unitofwork.ProcessState(uow, self, False, state) if parent_in_cycles: parent_saves = unitofwork.SaveUpdateState(uow, state) if child_in_cycles: child_actions = [] for child_state, child in sum_: if child_state not in uow.states: child_action = (None, None) else: (deleted, listonly) = uow.states[child_state] if deleted: child_action = ( unitofwork.DeleteState(uow, child_state), True, ) else: child_action = ( unitofwork.SaveUpdateState(uow, child_state), False, ) child_actions.append(child_action) # establish dependencies between our possibly per-state # parent action and our possibly per-state child action. for child_action, childisdelete in child_actions: self.per_state_dependencies( uow, parent_saves, parent_deletes, child_action, after_save, before_delete, isdelete, childisdelete, ) def presort_deletes(self, uowcommit, states): return False def presort_saves(self, uowcommit, states): return False def process_deletes(self, uowcommit, states): pass def process_saves(self, uowcommit, states): pass def prop_has_changes(self, uowcommit, states, isdelete): if not isdelete or self.passive_deletes: passive = ( attributes.PASSIVE_NO_INITIALIZE | attributes.INCLUDE_PENDING_MUTATIONS ) elif self.direction is MANYTOONE: # here, we were hoping to optimize having to fetch many-to-one # for history and ignore it, if there's no further cascades # to take place. however there are too many less common conditions # that still take place and tests in test_relationships / # test_cascade etc. will still fail. passive = attributes.PASSIVE_NO_FETCH_RELATED else: passive = ( attributes.PASSIVE_OFF | attributes.INCLUDE_PENDING_MUTATIONS ) for s in states: # TODO: add a high speed method # to InstanceState which returns: attribute # has a non-None value, or had one history = uowcommit.get_attribute_history(s, self.key, passive) if history and not history.empty(): return True else: return ( states and not self.prop._is_self_referential and self.mapper in uowcommit.mappers ) def _verify_canload(self, state): if self.prop.uselist and state is None: raise exc.FlushError( "Can't flush None value found in " "collection %s" % (self.prop,) ) elif state is not None and not self.mapper._canload( state, allow_subtypes=not self.enable_typechecks ): if self.mapper._canload(state, allow_subtypes=True): raise exc.FlushError( "Attempting to flush an item of type " "%(x)s as a member of collection " '"%(y)s". Expected an object of type ' "%(z)s or a polymorphic subclass of " "this type. If %(x)s is a subclass of " '%(z)s, configure mapper "%(zm)s" to ' "load this subtype polymorphically, or " "set enable_typechecks=False to allow " "any subtype to be accepted for flush. " % { "x": state.class_, "y": self.prop, "z": self.mapper.class_, "zm": self.mapper, } ) else: raise exc.FlushError( "Attempting to flush an item of type " "%(x)s as a member of collection " '"%(y)s". Expected an object of type ' "%(z)s or a polymorphic subclass of " "this type." % { "x": state.class_, "y": self.prop, "z": self.mapper.class_, } ) def _synchronize(self, state, child, associationrow, clearkeys, uowcommit): raise NotImplementedError() def _get_reversed_processed_set(self, uow): if not self.prop._reverse_property: return None process_key = tuple( sorted([self.key] + [p.key for p in self.prop._reverse_property]) ) return uow.memo(("reverse_key", process_key), set) def _post_update(self, state, uowcommit, related, is_m2o_delete=False): for x in related: if not is_m2o_delete or x is not None: uowcommit.register_post_update( state, [r for l, r in self.prop.synchronize_pairs] ) break def _pks_changed(self, uowcommit, state): raise NotImplementedError() def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self.prop) class OneToManyDP(DependencyProcessor): def per_property_dependencies( self, uow, parent_saves, child_saves, parent_deletes, child_deletes, after_save, before_delete, ): if self.post_update: child_post_updates = unitofwork.PostUpdateAll( uow, self.mapper.primary_base_mapper, False ) child_pre_updates = unitofwork.PostUpdateAll( uow, self.mapper.primary_base_mapper, True ) uow.dependencies.update( [ (child_saves, after_save), (parent_saves, after_save), (after_save, child_post_updates), (before_delete, child_pre_updates), (child_pre_updates, parent_deletes), (child_pre_updates, child_deletes), ] ) else: uow.dependencies.update( [ (parent_saves, after_save), (after_save, child_saves), (after_save, child_deletes), (child_saves, parent_deletes), (child_deletes, parent_deletes), (before_delete, child_saves), (before_delete, child_deletes), ] ) def per_state_dependencies( self, uow, save_parent, delete_parent, child_action, after_save, before_delete, isdelete, childisdelete, ): if self.post_update: child_post_updates = unitofwork.PostUpdateAll( uow, self.mapper.primary_base_mapper, False ) child_pre_updates = unitofwork.PostUpdateAll( uow, self.mapper.primary_base_mapper, True ) # TODO: this whole block is not covered # by any tests if not isdelete: if childisdelete: uow.dependencies.update( [ (child_action, after_save), (after_save, child_post_updates), ] ) else: uow.dependencies.update( [ (save_parent, after_save), (child_action, after_save), (after_save, child_post_updates), ] ) else: if childisdelete: uow.dependencies.update( [ (before_delete, child_pre_updates), (child_pre_updates, delete_parent), ] ) else: uow.dependencies.update( [ (before_delete, child_pre_updates), (child_pre_updates, delete_parent), ] ) elif not isdelete: uow.dependencies.update( [ (save_parent, after_save), (after_save, child_action), (save_parent, child_action), ] ) else: uow.dependencies.update( [(before_delete, child_action), (child_action, delete_parent)] ) def presort_deletes(self, uowcommit, states): # head object is being deleted, and we manage its list of # child objects the child objects have to have their # foreign key to the parent set to NULL should_null_fks = ( not self.cascade.delete and not self.passive_deletes == "all" ) for state in states: history = uowcommit.get_attribute_history( state, self.key, self._passive_delete_flag ) if history: for child in history.deleted: if child is not None and self.hasparent(child) is False: if self.cascade.delete_orphan: uowcommit.register_object(child, isdelete=True) else: uowcommit.register_object(child) if should_null_fks: for child in history.unchanged: if child is not None: uowcommit.register_object( child, operation="delete", prop=self.prop ) def presort_saves(self, uowcommit, states): children_added = uowcommit.memo(("children_added", self), set) should_null_fks = ( not self.cascade.delete_orphan and not self.passive_deletes == "all" ) for state in states: pks_changed = self._pks_changed(uowcommit, state) if not pks_changed or self.passive_updates: passive = ( attributes.PASSIVE_NO_INITIALIZE | attributes.INCLUDE_PENDING_MUTATIONS ) else: passive = ( attributes.PASSIVE_OFF | attributes.INCLUDE_PENDING_MUTATIONS ) history = uowcommit.get_attribute_history(state, self.key, passive) if history: for child in history.added: if child is not None: uowcommit.register_object( child, cancel_delete=True, operation="add", prop=self.prop, ) children_added.update(history.added) for child in history.deleted: if not self.cascade.delete_orphan: if should_null_fks: uowcommit.register_object( child, isdelete=False, operation="delete", prop=self.prop, ) elif self.hasparent(child) is False: uowcommit.register_object( child, isdelete=True, operation="delete", prop=self.prop, ) for c, m, st_, dct_ in self.mapper.cascade_iterator( "delete", child ): uowcommit.register_object(st_, isdelete=True) if pks_changed: if history: for child in history.unchanged: if child is not None: uowcommit.register_object( child, False, self.passive_updates, operation="pk change", prop=self.prop, ) def process_deletes(self, uowcommit, states): # head object is being deleted, and we manage its list of # child objects the child objects have to have their foreign # key to the parent set to NULL this phase can be called # safely for any cascade but is unnecessary if delete cascade # is on. if self.post_update or not self.passive_deletes == "all": children_added = uowcommit.memo(("children_added", self), set) for state in states: history = uowcommit.get_attribute_history( state, self.key, self._passive_delete_flag ) if history: for child in history.deleted: if ( child is not None and self.hasparent(child) is False ): self._synchronize( state, child, None, True, uowcommit, False ) if self.post_update and child: self._post_update(child, uowcommit, [state]) if self.post_update or not self.cascade.delete: for child in set(history.unchanged).difference( children_added ): if child is not None: self._synchronize( state, child, None, True, uowcommit, False ) if self.post_update and child: self._post_update( child, uowcommit, [state] ) # technically, we can even remove each child from the # collection here too. but this would be a somewhat # inconsistent behavior since it wouldn't happen # if the old parent wasn't deleted but child was moved. def process_saves(self, uowcommit, states): should_null_fks = ( not self.cascade.delete_orphan and not self.passive_deletes == "all" ) for state in states: history = uowcommit.get_attribute_history( state, self.key, attributes.PASSIVE_NO_INITIALIZE ) if history: for child in history.added: self._synchronize( state, child, None, False, uowcommit, False ) if child is not None and self.post_update: self._post_update(child, uowcommit, [state]) for child in history.deleted: if ( should_null_fks and not self.cascade.delete_orphan and not self.hasparent(child) ): self._synchronize( state, child, None, True, uowcommit, False ) if self._pks_changed(uowcommit, state): for child in history.unchanged: self._synchronize( state, child, None, False, uowcommit, True ) def _synchronize( self, state, child, associationrow, clearkeys, uowcommit, pks_changed ): source = state dest = child self._verify_canload(child) if dest is None or ( not self.post_update and uowcommit.is_deleted(dest) ): return if clearkeys: sync.clear(dest, self.mapper, self.prop.synchronize_pairs) else: sync.populate( source, self.parent, dest, self.mapper, self.prop.synchronize_pairs, uowcommit, self.passive_updates and pks_changed, ) def _pks_changed(self, uowcommit, state): return sync.source_modified( uowcommit, state, self.parent, self.prop.synchronize_pairs ) class ManyToOneDP(DependencyProcessor): def __init__(self, prop): DependencyProcessor.__init__(self, prop) for mapper in self.mapper.self_and_descendants: mapper._dependency_processors.append(DetectKeySwitch(prop)) def per_property_dependencies( self, uow, parent_saves, child_saves, parent_deletes, child_deletes, after_save, before_delete, ): if self.post_update: parent_post_updates = unitofwork.PostUpdateAll( uow, self.parent.primary_base_mapper, False ) parent_pre_updates = unitofwork.PostUpdateAll( uow, self.parent.primary_base_mapper, True ) uow.dependencies.update( [ (child_saves, after_save), (parent_saves, after_save), (after_save, parent_post_updates), (after_save, parent_pre_updates), (before_delete, parent_pre_updates), (parent_pre_updates, child_deletes), (parent_pre_updates, parent_deletes), ] ) else: uow.dependencies.update( [ (child_saves, after_save), (after_save, parent_saves), (parent_saves, child_deletes), (parent_deletes, child_deletes), ] ) def per_state_dependencies( self, uow, save_parent, delete_parent, child_action, after_save, before_delete, isdelete, childisdelete, ): if self.post_update: if not isdelete: parent_post_updates = unitofwork.PostUpdateAll( uow, self.parent.primary_base_mapper, False ) if childisdelete: uow.dependencies.update( [ (after_save, parent_post_updates), (parent_post_updates, child_action), ] ) else: uow.dependencies.update( [ (save_parent, after_save), (child_action, after_save), (after_save, parent_post_updates), ] ) else: parent_pre_updates = unitofwork.PostUpdateAll( uow, self.parent.primary_base_mapper, True ) uow.dependencies.update( [ (before_delete, parent_pre_updates), (parent_pre_updates, delete_parent), (parent_pre_updates, child_action), ] ) elif not isdelete: if not childisdelete: uow.dependencies.update( [(child_action, after_save), (after_save, save_parent)] ) else: uow.dependencies.update([(after_save, save_parent)]) else: if childisdelete: uow.dependencies.update([(delete_parent, child_action)]) def presort_deletes(self, uowcommit, states): if self.cascade.delete or self.cascade.delete_orphan: for state in states: history = uowcommit.get_attribute_history( state, self.key, self._passive_delete_flag ) if history: if self.cascade.delete_orphan: todelete = history.sum() else: todelete = history.non_deleted() for child in todelete: if child is None: continue uowcommit.register_object( child, isdelete=True, operation="delete", prop=self.prop, ) t = self.mapper.cascade_iterator("delete", child) for c, m, st_, dct_ in t: uowcommit.register_object(st_, isdelete=True) def presort_saves(self, uowcommit, states): for state in states: uowcommit.register_object(state, operation="add", prop=self.prop) if self.cascade.delete_orphan: history = uowcommit.get_attribute_history( state, self.key, self._passive_delete_flag ) if history: for child in history.deleted: if self.hasparent(child) is False: uowcommit.register_object( child, isdelete=True, operation="delete", prop=self.prop, ) t = self.mapper.cascade_iterator("delete", child) for c, m, st_, dct_ in t: uowcommit.register_object(st_, isdelete=True) def process_deletes(self, uowcommit, states): if ( self.post_update and not self.cascade.delete_orphan and not self.passive_deletes == "all" ): # post_update means we have to update our # row to not reference the child object # before we can DELETE the row for state in states: self._synchronize(state, None, None, True, uowcommit) if state and self.post_update: history = uowcommit.get_attribute_history( state, self.key, self._passive_delete_flag ) if history: self._post_update( state, uowcommit, history.sum(), is_m2o_delete=True ) def process_saves(self, uowcommit, states): for state in states: history = uowcommit.get_attribute_history( state, self.key, attributes.PASSIVE_NO_INITIALIZE ) if history: if history.added: for child in history.added: self._synchronize( state, child, None, False, uowcommit, "add" ) elif history.deleted: self._synchronize( state, None, None, True, uowcommit, "delete" ) if self.post_update: self._post_update(state, uowcommit, history.sum()) def _synchronize( self, state, child, associationrow, clearkeys, uowcommit, operation=None, ): if state is None or ( not self.post_update and uowcommit.is_deleted(state) ): return if ( operation is not None and child is not None and not uowcommit.session._contains_state(child) ): util.warn( "Object of type %s not in session, %s " "operation along '%s' won't proceed" % (mapperutil.state_class_str(child), operation, self.prop) ) return if clearkeys or child is None: sync.clear(state, self.parent, self.prop.synchronize_pairs) else: self._verify_canload(child) sync.populate( child, self.mapper, state, self.parent, self.prop.synchronize_pairs, uowcommit, False, ) class DetectKeySwitch(DependencyProcessor): """For many-to-one relationships with no one-to-many backref, searches for parents through the unit of work when a primary key has changed and updates them. Theoretically, this approach could be expanded to support transparent deletion of objects referenced via many-to-one as well, although the current attribute system doesn't do enough bookkeeping for this to be efficient. """ def per_property_preprocessors(self, uow): if self.prop._reverse_property: if self.passive_updates: return else: if False in ( prop.passive_updates for prop in self.prop._reverse_property ): return uow.register_preprocessor(self, False) def per_property_flush_actions(self, uow): parent_saves = unitofwork.SaveUpdateAll(uow, self.parent.base_mapper) after_save = unitofwork.ProcessAll(uow, self, False, False) uow.dependencies.update([(parent_saves, after_save)]) def per_state_flush_actions(self, uow, states, isdelete): pass def presort_deletes(self, uowcommit, states): pass def presort_saves(self, uow, states): if not self.passive_updates: # for non-passive updates, register in the preprocess stage # so that mapper save_obj() gets a hold of changes self._process_key_switches(states, uow) def prop_has_changes(self, uow, states, isdelete): if not isdelete and self.passive_updates: d = self._key_switchers(uow, states) return bool(d) return False def process_deletes(self, uowcommit, states): assert False def process_saves(self, uowcommit, states): # for passive updates, register objects in the process stage # so that we avoid ManyToOneDP's registering the object without # the listonly flag in its own preprocess stage (results in UPDATE) # statements being emitted assert self.passive_updates self._process_key_switches(states, uowcommit) def _key_switchers(self, uow, states): switched, notswitched = uow.memo( ("pk_switchers", self), lambda: (set(), set()) ) allstates = switched.union(notswitched) for s in states: if s not in allstates: if self._pks_changed(uow, s): switched.add(s) else: notswitched.add(s) return switched def _process_key_switches(self, deplist, uowcommit): switchers = self._key_switchers(uowcommit, deplist) if switchers: # if primary key values have actually changed somewhere, perform # a linear search through the UOW in search of a parent. for state in uowcommit.session.identity_map.all_states(): if not issubclass(state.class_, self.parent.class_): continue dict_ = state.dict related = state.get_impl(self.key).get( state, dict_, passive=self._passive_update_flag ) if ( related is not attributes.PASSIVE_NO_RESULT and related is not None ): if self.prop.uselist: if not related: continue related_obj = related[0] else: related_obj = related related_state = attributes.instance_state(related_obj) if related_state in switchers: uowcommit.register_object( state, False, self.passive_updates ) sync.populate( related_state, self.mapper, state, self.parent, self.prop.synchronize_pairs, uowcommit, self.passive_updates, ) def _pks_changed(self, uowcommit, state): return bool(state.key) and sync.source_modified( uowcommit, state, self.mapper, self.prop.synchronize_pairs ) class ManyToManyDP(DependencyProcessor): def per_property_dependencies( self, uow, parent_saves, child_saves, parent_deletes, child_deletes, after_save, before_delete, ): uow.dependencies.update( [ (parent_saves, after_save), (child_saves, after_save), (after_save, child_deletes), # a rowswitch on the parent from deleted to saved # can make this one occur, as the "save" may remove # an element from the # "deleted" list before we have a chance to # process its child rows (before_delete, parent_saves), (before_delete, parent_deletes), (before_delete, child_deletes), (before_delete, child_saves), ] ) def per_state_dependencies( self, uow, save_parent, delete_parent, child_action, after_save, before_delete, isdelete, childisdelete, ): if not isdelete: if childisdelete: uow.dependencies.update( [(save_parent, after_save), (after_save, child_action)] ) else: uow.dependencies.update( [(save_parent, after_save), (child_action, after_save)] ) else: uow.dependencies.update( [(before_delete, child_action), (before_delete, delete_parent)] ) def presort_deletes(self, uowcommit, states): # TODO: no tests fail if this whole # thing is removed !!!! if not self.passive_deletes: # if no passive deletes, load history on # the collection, so that prop_has_changes() # returns True for state in states: uowcommit.get_attribute_history( state, self.key, self._passive_delete_flag ) def presort_saves(self, uowcommit, states): if not self.passive_updates: # if no passive updates, load history on # each collection where parent has changed PK, # so that prop_has_changes() returns True for state in states: if self._pks_changed(uowcommit, state): history = uowcommit.get_attribute_history( state, self.key, attributes.PASSIVE_OFF ) if not self.cascade.delete_orphan: return # check for child items removed from the collection # if delete_orphan check is turned on. for state in states: history = uowcommit.get_attribute_history( state, self.key, attributes.PASSIVE_NO_INITIALIZE ) if history: for child in history.deleted: if self.hasparent(child) is False: uowcommit.register_object( child, isdelete=True, operation="delete", prop=self.prop, ) for c, m, st_, dct_ in self.mapper.cascade_iterator( "delete", child ): uowcommit.register_object(st_, isdelete=True) def process_deletes(self, uowcommit, states): secondary_delete = [] secondary_insert = [] secondary_update = [] processed = self._get_reversed_processed_set(uowcommit) tmp = set() for state in states: # this history should be cached already, as # we loaded it in preprocess_deletes history = uowcommit.get_attribute_history( state, self.key, self._passive_delete_flag ) if history: for child in history.non_added(): if child is None or ( processed is not None and (state, child) in processed ): continue associationrow = {} if not self._synchronize( state, child, associationrow, False, uowcommit, "delete", ): continue secondary_delete.append(associationrow) tmp.update((c, state) for c in history.non_added()) if processed is not None: processed.update(tmp) self._run_crud( uowcommit, secondary_insert, secondary_update, secondary_delete ) def process_saves(self, uowcommit, states): secondary_delete = [] secondary_insert = [] secondary_update = [] processed = self._get_reversed_processed_set(uowcommit) tmp = set() for state in states: need_cascade_pks = not self.passive_updates and self._pks_changed( uowcommit, state ) if need_cascade_pks: passive = ( attributes.PASSIVE_OFF | attributes.INCLUDE_PENDING_MUTATIONS ) else: passive = ( attributes.PASSIVE_NO_INITIALIZE | attributes.INCLUDE_PENDING_MUTATIONS ) history = uowcommit.get_attribute_history(state, self.key, passive) if history: for child in history.added: if processed is not None and (state, child) in processed: continue associationrow = {} if not self._synchronize( state, child, associationrow, False, uowcommit, "add" ): continue secondary_insert.append(associationrow) for child in history.deleted: if processed is not None and (state, child) in processed: continue associationrow = {} if not self._synchronize( state, child, associationrow, False, uowcommit, "delete", ): continue secondary_delete.append(associationrow) tmp.update((c, state) for c in history.added + history.deleted) if need_cascade_pks: for child in history.unchanged: associationrow = {} sync.update( state, self.parent, associationrow, "old_", self.prop.synchronize_pairs, ) sync.update( child, self.mapper, associationrow, "old_", self.prop.secondary_synchronize_pairs, ) secondary_update.append(associationrow) if processed is not None: processed.update(tmp) self._run_crud( uowcommit, secondary_insert, secondary_update, secondary_delete ) def _run_crud( self, uowcommit, secondary_insert, secondary_update, secondary_delete ): connection = uowcommit.transaction.connection(self.mapper) if secondary_delete: associationrow = secondary_delete[0] statement = self.secondary.delete().where( sql.and_( *[ c == sql.bindparam(c.key, type_=c.type) for c in self.secondary.c if c.key in associationrow ] ) ) result = connection.execute(statement, secondary_delete) if ( result.supports_sane_multi_rowcount() ) and result.rowcount != len(secondary_delete): raise exc.StaleDataError( "DELETE statement on table '%s' expected to delete " "%d row(s); Only %d were matched." % ( self.secondary.description, len(secondary_delete), result.rowcount, ) ) if secondary_update: associationrow = secondary_update[0] statement = self.secondary.update().where( sql.and_( *[ c == sql.bindparam("old_" + c.key, type_=c.type) for c in self.secondary.c if c.key in associationrow ] ) ) result = connection.execute(statement, secondary_update) if ( result.supports_sane_multi_rowcount() ) and result.rowcount != len(secondary_update): raise exc.StaleDataError( "UPDATE statement on table '%s' expected to update " "%d row(s); Only %d were matched." % ( self.secondary.description, len(secondary_update), result.rowcount, ) ) if secondary_insert: statement = self.secondary.insert() connection.execute(statement, secondary_insert) def _synchronize( self, state, child, associationrow, clearkeys, uowcommit, operation ): # this checks for None if uselist=True self._verify_canload(child) # but if uselist=False we get here. If child is None, # no association row can be generated, so return. if child is None: return False if child is not None and not uowcommit.session._contains_state(child): if not child.deleted: util.warn( "Object of type %s not in session, %s " "operation along '%s' won't proceed" % (mapperutil.state_class_str(child), operation, self.prop) ) return False sync.populate_dict( state, self.parent, associationrow, self.prop.synchronize_pairs ) sync.populate_dict( child, self.mapper, associationrow, self.prop.secondary_synchronize_pairs, ) return True def _pks_changed(self, uowcommit, state): return sync.source_modified( uowcommit, state, self.parent, self.prop.synchronize_pairs ) _direction_to_processor = { ONETOMANY: OneToManyDP, MANYTOONE: ManyToOneDP, MANYTOMANY: ManyToManyDP, }
Save