SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. sqlalchemy/__init__.py +298 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1306 @@
1
+ # orm/dependency.py
2
+ # Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+ # mypy: ignore-errors
8
+
9
+
10
+ """Relationship dependencies."""
11
+
12
+ from __future__ import annotations
13
+
14
+ from . import attributes
15
+ from . import exc
16
+ from . import sync
17
+ from . import unitofwork
18
+ from . import util as mapperutil
19
+ from .interfaces import MANYTOMANY
20
+ from .interfaces import MANYTOONE
21
+ from .interfaces import ONETOMANY
22
+ from .. import exc as sa_exc
23
+ from .. import sql
24
+ from .. import util
25
+
26
+
27
+ class _DependencyProcessor:
28
+ def __init__(self, prop):
29
+ self.prop = prop
30
+ self.cascade = prop.cascade
31
+ self.mapper = prop.mapper
32
+ self.parent = prop.parent
33
+ self.secondary = prop.secondary
34
+ self.direction = prop.direction
35
+ self.post_update = prop.post_update
36
+ self.passive_deletes = prop.passive_deletes
37
+ self.passive_updates = prop.passive_updates
38
+ self.enable_typechecks = prop.enable_typechecks
39
+ if self.passive_deletes:
40
+ self._passive_delete_flag = attributes.PASSIVE_NO_INITIALIZE
41
+ else:
42
+ self._passive_delete_flag = attributes.PASSIVE_OFF
43
+ if self.passive_updates:
44
+ self._passive_update_flag = attributes.PASSIVE_NO_INITIALIZE
45
+ else:
46
+ self._passive_update_flag = attributes.PASSIVE_OFF
47
+
48
+ self.sort_key = "%s_%s" % (self.parent._sort_key, prop.key)
49
+ self.key = prop.key
50
+ if not self.prop.synchronize_pairs:
51
+ raise sa_exc.ArgumentError(
52
+ "Can't build a DependencyProcessor for relationship %s. "
53
+ "No target attributes to populate between parent and "
54
+ "child are present" % self.prop
55
+ )
56
+
57
+ @classmethod
58
+ def from_relationship(cls, prop):
59
+ return _direction_to_processor[prop.direction](prop)
60
+
61
+ def hasparent(self, state):
62
+ """return True if the given object instance has a parent,
63
+ according to the ``InstrumentedAttribute`` handled by this
64
+ ``DependencyProcessor``.
65
+
66
+ """
67
+ return self.parent.class_manager.get_impl(self.key).hasparent(state)
68
+
69
+ def per_property_preprocessors(self, uow):
70
+ """establish actions and dependencies related to a flush.
71
+
72
+ These actions will operate on all relevant states in
73
+ the aggregate.
74
+
75
+ """
76
+ uow.register_preprocessor(self, True)
77
+
78
+ def per_property_flush_actions(self, uow):
79
+ after_save = unitofwork._ProcessAll(uow, self, False, True)
80
+ before_delete = unitofwork._ProcessAll(uow, self, True, True)
81
+
82
+ parent_saves = unitofwork._SaveUpdateAll(
83
+ uow, self.parent.primary_base_mapper
84
+ )
85
+ child_saves = unitofwork._SaveUpdateAll(
86
+ uow, self.mapper.primary_base_mapper
87
+ )
88
+
89
+ parent_deletes = unitofwork._DeleteAll(
90
+ uow, self.parent.primary_base_mapper
91
+ )
92
+ child_deletes = unitofwork._DeleteAll(
93
+ uow, self.mapper.primary_base_mapper
94
+ )
95
+
96
+ self.per_property_dependencies(
97
+ uow,
98
+ parent_saves,
99
+ child_saves,
100
+ parent_deletes,
101
+ child_deletes,
102
+ after_save,
103
+ before_delete,
104
+ )
105
+
106
+ def per_state_flush_actions(self, uow, states, isdelete):
107
+ """establish actions and dependencies related to a flush.
108
+
109
+ These actions will operate on all relevant states
110
+ individually. This occurs only if there are cycles
111
+ in the 'aggregated' version of events.
112
+
113
+ """
114
+
115
+ child_base_mapper = self.mapper.primary_base_mapper
116
+ child_saves = unitofwork._SaveUpdateAll(uow, child_base_mapper)
117
+ child_deletes = unitofwork._DeleteAll(uow, child_base_mapper)
118
+
119
+ # locate and disable the aggregate processors
120
+ # for this dependency
121
+
122
+ if isdelete:
123
+ before_delete = unitofwork._ProcessAll(uow, self, True, True)
124
+ before_delete.disabled = True
125
+ else:
126
+ after_save = unitofwork._ProcessAll(uow, self, False, True)
127
+ after_save.disabled = True
128
+
129
+ # check if the "child" side is part of the cycle
130
+
131
+ if child_saves not in uow.cycles:
132
+ # based on the current dependencies we use, the saves/
133
+ # deletes should always be in the 'cycles' collection
134
+ # together. if this changes, we will have to break up
135
+ # this method a bit more.
136
+ assert child_deletes not in uow.cycles
137
+
138
+ # child side is not part of the cycle, so we will link per-state
139
+ # actions to the aggregate "saves", "deletes" actions
140
+ child_actions = [(child_saves, False), (child_deletes, True)]
141
+ child_in_cycles = False
142
+ else:
143
+ child_in_cycles = True
144
+
145
+ # check if the "parent" side is part of the cycle
146
+ if not isdelete:
147
+ parent_saves = unitofwork._SaveUpdateAll(
148
+ uow, self.parent.base_mapper
149
+ )
150
+ parent_deletes = before_delete = None
151
+ if parent_saves in uow.cycles:
152
+ parent_in_cycles = True
153
+ else:
154
+ parent_deletes = unitofwork._DeleteAll(
155
+ uow, self.parent.base_mapper
156
+ )
157
+ parent_saves = after_save = None
158
+ if parent_deletes in uow.cycles:
159
+ parent_in_cycles = True
160
+
161
+ # now create actions /dependencies for each state.
162
+
163
+ for state in states:
164
+ # detect if there's anything changed or loaded
165
+ # by a preprocessor on this state/attribute. In the
166
+ # case of deletes we may try to load missing items here as well.
167
+ sum_ = state.manager[self.key].impl.get_all_pending(
168
+ state,
169
+ state.dict,
170
+ (
171
+ self._passive_delete_flag
172
+ if isdelete
173
+ else attributes.PASSIVE_NO_INITIALIZE
174
+ ),
175
+ )
176
+
177
+ if not sum_:
178
+ continue
179
+
180
+ if isdelete:
181
+ before_delete = unitofwork._ProcessState(
182
+ uow, self, True, state
183
+ )
184
+ if parent_in_cycles:
185
+ parent_deletes = unitofwork._DeleteState(uow, state)
186
+ else:
187
+ after_save = unitofwork._ProcessState(uow, self, False, state)
188
+ if parent_in_cycles:
189
+ parent_saves = unitofwork._SaveUpdateState(uow, state)
190
+
191
+ if child_in_cycles:
192
+ child_actions = []
193
+ for child_state, child in sum_:
194
+ if child_state not in uow.states:
195
+ child_action = (None, None)
196
+ else:
197
+ (deleted, listonly) = uow.states[child_state]
198
+ if deleted:
199
+ child_action = (
200
+ unitofwork._DeleteState(uow, child_state),
201
+ True,
202
+ )
203
+ else:
204
+ child_action = (
205
+ unitofwork._SaveUpdateState(uow, child_state),
206
+ False,
207
+ )
208
+ child_actions.append(child_action)
209
+
210
+ # establish dependencies between our possibly per-state
211
+ # parent action and our possibly per-state child action.
212
+ for child_action, childisdelete in child_actions:
213
+ self.per_state_dependencies(
214
+ uow,
215
+ parent_saves,
216
+ parent_deletes,
217
+ child_action,
218
+ after_save,
219
+ before_delete,
220
+ isdelete,
221
+ childisdelete,
222
+ )
223
+
224
+ def presort_deletes(self, uowcommit, states):
225
+ return False
226
+
227
+ def presort_saves(self, uowcommit, states):
228
+ return False
229
+
230
+ def process_deletes(self, uowcommit, states):
231
+ pass
232
+
233
+ def process_saves(self, uowcommit, states):
234
+ pass
235
+
236
+ def prop_has_changes(self, uowcommit, states, isdelete):
237
+ if not isdelete or self.passive_deletes:
238
+ passive = (
239
+ attributes.PASSIVE_NO_INITIALIZE
240
+ | attributes.INCLUDE_PENDING_MUTATIONS
241
+ )
242
+ elif self.direction is MANYTOONE:
243
+ # here, we were hoping to optimize having to fetch many-to-one
244
+ # for history and ignore it, if there's no further cascades
245
+ # to take place. however there are too many less common conditions
246
+ # that still take place and tests in test_relationships /
247
+ # test_cascade etc. will still fail.
248
+ passive = attributes.PASSIVE_NO_FETCH_RELATED
249
+ else:
250
+ passive = (
251
+ attributes.PASSIVE_OFF | attributes.INCLUDE_PENDING_MUTATIONS
252
+ )
253
+
254
+ for s in states:
255
+ # TODO: add a high speed method
256
+ # to InstanceState which returns: attribute
257
+ # has a non-None value, or had one
258
+ history = uowcommit.get_attribute_history(s, self.key, passive)
259
+ if history and not history.empty():
260
+ return True
261
+ else:
262
+ return (
263
+ states
264
+ and not self.prop._is_self_referential
265
+ and self.mapper in uowcommit.mappers
266
+ )
267
+
268
+ def _verify_canload(self, state):
269
+ if self.prop.uselist and state is None:
270
+ raise exc.FlushError(
271
+ "Can't flush None value found in "
272
+ "collection %s" % (self.prop,)
273
+ )
274
+ elif state is not None and not self.mapper._canload(
275
+ state, allow_subtypes=not self.enable_typechecks
276
+ ):
277
+ if self.mapper._canload(state, allow_subtypes=True):
278
+ raise exc.FlushError(
279
+ "Attempting to flush an item of type "
280
+ "%(x)s as a member of collection "
281
+ '"%(y)s". Expected an object of type '
282
+ "%(z)s or a polymorphic subclass of "
283
+ "this type. If %(x)s is a subclass of "
284
+ '%(z)s, configure mapper "%(zm)s" to '
285
+ "load this subtype polymorphically, or "
286
+ "set enable_typechecks=False to allow "
287
+ "any subtype to be accepted for flush. "
288
+ % {
289
+ "x": state.class_,
290
+ "y": self.prop,
291
+ "z": self.mapper.class_,
292
+ "zm": self.mapper,
293
+ }
294
+ )
295
+ else:
296
+ raise exc.FlushError(
297
+ "Attempting to flush an item of type "
298
+ "%(x)s as a member of collection "
299
+ '"%(y)s". Expected an object of type '
300
+ "%(z)s or a polymorphic subclass of "
301
+ "this type."
302
+ % {
303
+ "x": state.class_,
304
+ "y": self.prop,
305
+ "z": self.mapper.class_,
306
+ }
307
+ )
308
+
309
+ def _synchronize(self, state, child, associationrow, clearkeys, uowcommit):
310
+ raise NotImplementedError()
311
+
312
+ def _get_reversed_processed_set(self, uow):
313
+ if not self.prop._reverse_property:
314
+ return None
315
+
316
+ process_key = tuple(
317
+ sorted([self.key] + [p.key for p in self.prop._reverse_property])
318
+ )
319
+ return uow.memo(("reverse_key", process_key), set)
320
+
321
+ def _post_update(self, state, uowcommit, related, is_m2o_delete=False):
322
+ for x in related:
323
+ if not is_m2o_delete or x is not None:
324
+ uowcommit.register_post_update(
325
+ state, [r for l, r in self.prop.synchronize_pairs]
326
+ )
327
+ break
328
+
329
+ def _pks_changed(self, uowcommit, state):
330
+ raise NotImplementedError()
331
+
332
+ def __repr__(self):
333
+ return "%s(%s)" % (self.__class__.__name__, self.prop)
334
+
335
+
336
+ class _OneToManyDP(_DependencyProcessor):
337
+ def per_property_dependencies(
338
+ self,
339
+ uow,
340
+ parent_saves,
341
+ child_saves,
342
+ parent_deletes,
343
+ child_deletes,
344
+ after_save,
345
+ before_delete,
346
+ ):
347
+ if self.post_update:
348
+ child_post_updates = unitofwork._PostUpdateAll(
349
+ uow, self.mapper.primary_base_mapper, False
350
+ )
351
+ child_pre_updates = unitofwork._PostUpdateAll(
352
+ uow, self.mapper.primary_base_mapper, True
353
+ )
354
+
355
+ uow.dependencies.update(
356
+ [
357
+ (child_saves, after_save),
358
+ (parent_saves, after_save),
359
+ (after_save, child_post_updates),
360
+ (before_delete, child_pre_updates),
361
+ (child_pre_updates, parent_deletes),
362
+ (child_pre_updates, child_deletes),
363
+ ]
364
+ )
365
+ else:
366
+ uow.dependencies.update(
367
+ [
368
+ (parent_saves, after_save),
369
+ (after_save, child_saves),
370
+ (after_save, child_deletes),
371
+ (child_saves, parent_deletes),
372
+ (child_deletes, parent_deletes),
373
+ (before_delete, child_saves),
374
+ (before_delete, child_deletes),
375
+ ]
376
+ )
377
+
378
+ def per_state_dependencies(
379
+ self,
380
+ uow,
381
+ save_parent,
382
+ delete_parent,
383
+ child_action,
384
+ after_save,
385
+ before_delete,
386
+ isdelete,
387
+ childisdelete,
388
+ ):
389
+ if self.post_update:
390
+ child_post_updates = unitofwork._PostUpdateAll(
391
+ uow, self.mapper.primary_base_mapper, False
392
+ )
393
+ child_pre_updates = unitofwork._PostUpdateAll(
394
+ uow, self.mapper.primary_base_mapper, True
395
+ )
396
+
397
+ # TODO: this whole block is not covered
398
+ # by any tests
399
+ if not isdelete:
400
+ if childisdelete:
401
+ uow.dependencies.update(
402
+ [
403
+ (child_action, after_save),
404
+ (after_save, child_post_updates),
405
+ ]
406
+ )
407
+ else:
408
+ uow.dependencies.update(
409
+ [
410
+ (save_parent, after_save),
411
+ (child_action, after_save),
412
+ (after_save, child_post_updates),
413
+ ]
414
+ )
415
+ else:
416
+ if childisdelete:
417
+ uow.dependencies.update(
418
+ [
419
+ (before_delete, child_pre_updates),
420
+ (child_pre_updates, delete_parent),
421
+ ]
422
+ )
423
+ else:
424
+ uow.dependencies.update(
425
+ [
426
+ (before_delete, child_pre_updates),
427
+ (child_pre_updates, delete_parent),
428
+ ]
429
+ )
430
+ elif not isdelete:
431
+ uow.dependencies.update(
432
+ [
433
+ (save_parent, after_save),
434
+ (after_save, child_action),
435
+ (save_parent, child_action),
436
+ ]
437
+ )
438
+ else:
439
+ uow.dependencies.update(
440
+ [(before_delete, child_action), (child_action, delete_parent)]
441
+ )
442
+
443
+ def presort_deletes(self, uowcommit, states):
444
+ # head object is being deleted, and we manage its list of
445
+ # child objects the child objects have to have their
446
+ # foreign key to the parent set to NULL
447
+ should_null_fks = (
448
+ not self.cascade.delete and not self.passive_deletes == "all"
449
+ )
450
+
451
+ for state in states:
452
+ history = uowcommit.get_attribute_history(
453
+ state, self.key, self._passive_delete_flag
454
+ )
455
+ if history:
456
+ for child in history.deleted:
457
+ if child is not None and self.hasparent(child) is False:
458
+ if self.cascade.delete_orphan:
459
+ uowcommit.register_object(child, isdelete=True)
460
+ else:
461
+ uowcommit.register_object(child)
462
+
463
+ if should_null_fks:
464
+ for child in history.unchanged:
465
+ if child is not None:
466
+ uowcommit.register_object(
467
+ child, operation="delete", prop=self.prop
468
+ )
469
+
470
+ def presort_saves(self, uowcommit, states):
471
+ children_added = uowcommit.memo(("children_added", self), set)
472
+
473
+ should_null_fks = (
474
+ not self.cascade.delete_orphan
475
+ and not self.passive_deletes == "all"
476
+ )
477
+
478
+ for state in states:
479
+ pks_changed = self._pks_changed(uowcommit, state)
480
+
481
+ if not pks_changed or self.passive_updates:
482
+ passive = (
483
+ attributes.PASSIVE_NO_INITIALIZE
484
+ | attributes.INCLUDE_PENDING_MUTATIONS
485
+ )
486
+ else:
487
+ passive = (
488
+ attributes.PASSIVE_OFF
489
+ | attributes.INCLUDE_PENDING_MUTATIONS
490
+ )
491
+
492
+ history = uowcommit.get_attribute_history(state, self.key, passive)
493
+ if history:
494
+ for child in history.added:
495
+ if child is not None:
496
+ uowcommit.register_object(
497
+ child,
498
+ cancel_delete=True,
499
+ operation="add",
500
+ prop=self.prop,
501
+ )
502
+
503
+ children_added.update(history.added)
504
+
505
+ for child in history.deleted:
506
+ if not self.cascade.delete_orphan:
507
+ if should_null_fks:
508
+ uowcommit.register_object(
509
+ child,
510
+ isdelete=False,
511
+ operation="delete",
512
+ prop=self.prop,
513
+ )
514
+ elif self.hasparent(child) is False:
515
+ uowcommit.register_object(
516
+ child,
517
+ isdelete=True,
518
+ operation="delete",
519
+ prop=self.prop,
520
+ )
521
+ for c, m, st_, dct_ in self.mapper.cascade_iterator(
522
+ "delete", child
523
+ ):
524
+ uowcommit.register_object(st_, isdelete=True)
525
+
526
+ if pks_changed:
527
+ if history:
528
+ for child in history.unchanged:
529
+ if child is not None:
530
+ uowcommit.register_object(
531
+ child,
532
+ False,
533
+ self.passive_updates,
534
+ operation="pk change",
535
+ prop=self.prop,
536
+ )
537
+
538
+ def process_deletes(self, uowcommit, states):
539
+ # head object is being deleted, and we manage its list of
540
+ # child objects the child objects have to have their foreign
541
+ # key to the parent set to NULL this phase can be called
542
+ # safely for any cascade but is unnecessary if delete cascade
543
+ # is on.
544
+
545
+ if self.post_update or not self.passive_deletes == "all":
546
+ children_added = uowcommit.memo(("children_added", self), set)
547
+
548
+ for state in states:
549
+ history = uowcommit.get_attribute_history(
550
+ state, self.key, self._passive_delete_flag
551
+ )
552
+ if history:
553
+ for child in history.deleted:
554
+ if (
555
+ child is not None
556
+ and self.hasparent(child) is False
557
+ ):
558
+ self._synchronize(
559
+ state, child, None, True, uowcommit, False
560
+ )
561
+ if self.post_update and child:
562
+ self._post_update(child, uowcommit, [state])
563
+
564
+ if self.post_update or not self.cascade.delete:
565
+ for child in set(history.unchanged).difference(
566
+ children_added
567
+ ):
568
+ if child is not None:
569
+ self._synchronize(
570
+ state, child, None, True, uowcommit, False
571
+ )
572
+ if self.post_update and child:
573
+ self._post_update(
574
+ child, uowcommit, [state]
575
+ )
576
+
577
+ # technically, we can even remove each child from the
578
+ # collection here too. but this would be a somewhat
579
+ # inconsistent behavior since it wouldn't happen
580
+ # if the old parent wasn't deleted but child was moved.
581
+
582
+ def process_saves(self, uowcommit, states):
583
+ should_null_fks = (
584
+ not self.cascade.delete_orphan
585
+ and not self.passive_deletes == "all"
586
+ )
587
+
588
+ for state in states:
589
+ history = uowcommit.get_attribute_history(
590
+ state, self.key, attributes.PASSIVE_NO_INITIALIZE
591
+ )
592
+ if history:
593
+ for child in history.added:
594
+ self._synchronize(
595
+ state, child, None, False, uowcommit, False
596
+ )
597
+ if child is not None and self.post_update:
598
+ self._post_update(child, uowcommit, [state])
599
+
600
+ for child in history.deleted:
601
+ if (
602
+ should_null_fks
603
+ and not self.cascade.delete_orphan
604
+ and not self.hasparent(child)
605
+ ):
606
+ self._synchronize(
607
+ state, child, None, True, uowcommit, False
608
+ )
609
+
610
+ if self._pks_changed(uowcommit, state):
611
+ for child in history.unchanged:
612
+ self._synchronize(
613
+ state, child, None, False, uowcommit, True
614
+ )
615
+
616
+ def _synchronize(
617
+ self, state, child, associationrow, clearkeys, uowcommit, pks_changed
618
+ ):
619
+ source = state
620
+ dest = child
621
+ self._verify_canload(child)
622
+ if dest is None or (
623
+ not self.post_update and uowcommit.is_deleted(dest)
624
+ ):
625
+ return
626
+ if clearkeys:
627
+ sync._clear(dest, self.mapper, self.prop.synchronize_pairs)
628
+ else:
629
+ sync._populate(
630
+ source,
631
+ self.parent,
632
+ dest,
633
+ self.mapper,
634
+ self.prop.synchronize_pairs,
635
+ uowcommit,
636
+ self.passive_updates and pks_changed,
637
+ )
638
+
639
+ def _pks_changed(self, uowcommit, state):
640
+ return sync._source_modified(
641
+ uowcommit, state, self.parent, self.prop.synchronize_pairs
642
+ )
643
+
644
+
645
+ class _ManyToOneDP(_DependencyProcessor):
646
+ def __init__(self, prop):
647
+ _DependencyProcessor.__init__(self, prop)
648
+ for mapper in self.mapper.self_and_descendants:
649
+ mapper._dependency_processors.append(_DetectKeySwitch(prop))
650
+
651
+ def per_property_dependencies(
652
+ self,
653
+ uow,
654
+ parent_saves,
655
+ child_saves,
656
+ parent_deletes,
657
+ child_deletes,
658
+ after_save,
659
+ before_delete,
660
+ ):
661
+ if self.post_update:
662
+ parent_post_updates = unitofwork._PostUpdateAll(
663
+ uow, self.parent.primary_base_mapper, False
664
+ )
665
+ parent_pre_updates = unitofwork._PostUpdateAll(
666
+ uow, self.parent.primary_base_mapper, True
667
+ )
668
+
669
+ uow.dependencies.update(
670
+ [
671
+ (child_saves, after_save),
672
+ (parent_saves, after_save),
673
+ (after_save, parent_post_updates),
674
+ (after_save, parent_pre_updates),
675
+ (before_delete, parent_pre_updates),
676
+ (parent_pre_updates, child_deletes),
677
+ (parent_pre_updates, parent_deletes),
678
+ ]
679
+ )
680
+ else:
681
+ uow.dependencies.update(
682
+ [
683
+ (child_saves, after_save),
684
+ (after_save, parent_saves),
685
+ (parent_saves, child_deletes),
686
+ (parent_deletes, child_deletes),
687
+ ]
688
+ )
689
+
690
+ def per_state_dependencies(
691
+ self,
692
+ uow,
693
+ save_parent,
694
+ delete_parent,
695
+ child_action,
696
+ after_save,
697
+ before_delete,
698
+ isdelete,
699
+ childisdelete,
700
+ ):
701
+ if self.post_update:
702
+ if not isdelete:
703
+ parent_post_updates = unitofwork._PostUpdateAll(
704
+ uow, self.parent.primary_base_mapper, False
705
+ )
706
+ if childisdelete:
707
+ uow.dependencies.update(
708
+ [
709
+ (after_save, parent_post_updates),
710
+ (parent_post_updates, child_action),
711
+ ]
712
+ )
713
+ else:
714
+ uow.dependencies.update(
715
+ [
716
+ (save_parent, after_save),
717
+ (child_action, after_save),
718
+ (after_save, parent_post_updates),
719
+ ]
720
+ )
721
+ else:
722
+ parent_pre_updates = unitofwork._PostUpdateAll(
723
+ uow, self.parent.primary_base_mapper, True
724
+ )
725
+
726
+ uow.dependencies.update(
727
+ [
728
+ (before_delete, parent_pre_updates),
729
+ (parent_pre_updates, delete_parent),
730
+ (parent_pre_updates, child_action),
731
+ ]
732
+ )
733
+
734
+ elif not isdelete:
735
+ if not childisdelete:
736
+ uow.dependencies.update(
737
+ [(child_action, after_save), (after_save, save_parent)]
738
+ )
739
+ else:
740
+ uow.dependencies.update([(after_save, save_parent)])
741
+
742
+ else:
743
+ if childisdelete:
744
+ uow.dependencies.update([(delete_parent, child_action)])
745
+
746
+ def presort_deletes(self, uowcommit, states):
747
+ if self.cascade.delete or self.cascade.delete_orphan:
748
+ for state in states:
749
+ history = uowcommit.get_attribute_history(
750
+ state, self.key, self._passive_delete_flag
751
+ )
752
+ if history:
753
+ if self.cascade.delete_orphan:
754
+ todelete = history.sum()
755
+ else:
756
+ todelete = history.non_deleted()
757
+ for child in todelete:
758
+ if child is None:
759
+ continue
760
+ uowcommit.register_object(
761
+ child,
762
+ isdelete=True,
763
+ operation="delete",
764
+ prop=self.prop,
765
+ )
766
+ t = self.mapper.cascade_iterator("delete", child)
767
+ for c, m, st_, dct_ in t:
768
+ uowcommit.register_object(st_, isdelete=True)
769
+
770
+ def presort_saves(self, uowcommit, states):
771
+ for state in states:
772
+ uowcommit.register_object(state, operation="add", prop=self.prop)
773
+ if self.cascade.delete_orphan:
774
+ history = uowcommit.get_attribute_history(
775
+ state, self.key, self._passive_delete_flag
776
+ )
777
+ if history:
778
+ for child in history.deleted:
779
+ if self.hasparent(child) is False:
780
+ uowcommit.register_object(
781
+ child,
782
+ isdelete=True,
783
+ operation="delete",
784
+ prop=self.prop,
785
+ )
786
+
787
+ t = self.mapper.cascade_iterator("delete", child)
788
+ for c, m, st_, dct_ in t:
789
+ uowcommit.register_object(st_, isdelete=True)
790
+
791
+ def process_deletes(self, uowcommit, states):
792
+ if (
793
+ self.post_update
794
+ and not self.cascade.delete_orphan
795
+ and not self.passive_deletes == "all"
796
+ ):
797
+ # post_update means we have to update our
798
+ # row to not reference the child object
799
+ # before we can DELETE the row
800
+ for state in states:
801
+ self._synchronize(state, None, None, True, uowcommit)
802
+ if state and self.post_update:
803
+ history = uowcommit.get_attribute_history(
804
+ state, self.key, self._passive_delete_flag
805
+ )
806
+ if history:
807
+ self._post_update(
808
+ state, uowcommit, history.sum(), is_m2o_delete=True
809
+ )
810
+
811
+ def process_saves(self, uowcommit, states):
812
+ for state in states:
813
+ history = uowcommit.get_attribute_history(
814
+ state, self.key, attributes.PASSIVE_NO_INITIALIZE
815
+ )
816
+ if history:
817
+ if history.added:
818
+ for child in history.added:
819
+ self._synchronize(
820
+ state, child, None, False, uowcommit, "add"
821
+ )
822
+ elif history.deleted:
823
+ self._synchronize(
824
+ state, None, None, True, uowcommit, "delete"
825
+ )
826
+ if self.post_update:
827
+ self._post_update(state, uowcommit, history.sum())
828
+
829
+ def _synchronize(
830
+ self,
831
+ state,
832
+ child,
833
+ associationrow,
834
+ clearkeys,
835
+ uowcommit,
836
+ operation=None,
837
+ ):
838
+ if state is None or (
839
+ not self.post_update and uowcommit.is_deleted(state)
840
+ ):
841
+ return
842
+
843
+ if (
844
+ operation is not None
845
+ and child is not None
846
+ and not uowcommit.session._contains_state(child)
847
+ ):
848
+ util.warn(
849
+ "Object of type %s not in session, %s "
850
+ "operation along '%s' won't proceed"
851
+ % (mapperutil.state_class_str(child), operation, self.prop)
852
+ )
853
+ return
854
+
855
+ if clearkeys or child is None:
856
+ sync._clear(state, self.parent, self.prop.synchronize_pairs)
857
+ else:
858
+ self._verify_canload(child)
859
+ sync._populate(
860
+ child,
861
+ self.mapper,
862
+ state,
863
+ self.parent,
864
+ self.prop.synchronize_pairs,
865
+ uowcommit,
866
+ False,
867
+ )
868
+
869
+
870
+ class _DetectKeySwitch(_DependencyProcessor):
871
+ """For many-to-one relationships with no one-to-many backref,
872
+ searches for parents through the unit of work when a primary
873
+ key has changed and updates them.
874
+
875
+ Theoretically, this approach could be expanded to support transparent
876
+ deletion of objects referenced via many-to-one as well, although
877
+ the current attribute system doesn't do enough bookkeeping for this
878
+ to be efficient.
879
+
880
+ """
881
+
882
+ def per_property_preprocessors(self, uow):
883
+ if self.prop._reverse_property:
884
+ if self.passive_updates:
885
+ return
886
+ else:
887
+ if False in (
888
+ prop.passive_updates
889
+ for prop in self.prop._reverse_property
890
+ ):
891
+ return
892
+
893
+ uow.register_preprocessor(self, False)
894
+
895
+ def per_property_flush_actions(self, uow):
896
+ parent_saves = unitofwork._SaveUpdateAll(uow, self.parent.base_mapper)
897
+ after_save = unitofwork._ProcessAll(uow, self, False, False)
898
+ uow.dependencies.update([(parent_saves, after_save)])
899
+
900
+ def per_state_flush_actions(self, uow, states, isdelete):
901
+ pass
902
+
903
+ def presort_deletes(self, uowcommit, states):
904
+ pass
905
+
906
+ def presort_saves(self, uow, states):
907
+ if not self.passive_updates:
908
+ # for non-passive updates, register in the preprocess stage
909
+ # so that mapper save_obj() gets a hold of changes
910
+ self._process_key_switches(states, uow)
911
+
912
+ def prop_has_changes(self, uow, states, isdelete):
913
+ if not isdelete and self.passive_updates:
914
+ d = self._key_switchers(uow, states)
915
+ return bool(d)
916
+
917
+ return False
918
+
919
+ def process_deletes(self, uowcommit, states):
920
+ assert False
921
+
922
+ def process_saves(self, uowcommit, states):
923
+ # for passive updates, register objects in the process stage
924
+ # so that we avoid ManyToOneDP's registering the object without
925
+ # the listonly flag in its own preprocess stage (results in UPDATE)
926
+ # statements being emitted
927
+ assert self.passive_updates
928
+ self._process_key_switches(states, uowcommit)
929
+
930
+ def _key_switchers(self, uow, states):
931
+ switched, notswitched = uow.memo(
932
+ ("pk_switchers", self), lambda: (set(), set())
933
+ )
934
+
935
+ allstates = switched.union(notswitched)
936
+ for s in states:
937
+ if s not in allstates:
938
+ if self._pks_changed(uow, s):
939
+ switched.add(s)
940
+ else:
941
+ notswitched.add(s)
942
+ return switched
943
+
944
+ def _process_key_switches(self, deplist, uowcommit):
945
+ switchers = self._key_switchers(uowcommit, deplist)
946
+ if switchers:
947
+ # if primary key values have actually changed somewhere, perform
948
+ # a linear search through the UOW in search of a parent.
949
+ for state in uowcommit.session.identity_map.all_states():
950
+ if not issubclass(state.class_, self.parent.class_):
951
+ continue
952
+ dict_ = state.dict
953
+ related = state.get_impl(self.key).get(
954
+ state, dict_, passive=self._passive_update_flag
955
+ )
956
+ if (
957
+ related is not attributes.PASSIVE_NO_RESULT
958
+ and related is not None
959
+ ):
960
+ if self.prop.uselist:
961
+ if not related:
962
+ continue
963
+ related_obj = related[0]
964
+ else:
965
+ related_obj = related
966
+ related_state = attributes.instance_state(related_obj)
967
+ if related_state in switchers:
968
+ uowcommit.register_object(
969
+ state, False, self.passive_updates
970
+ )
971
+ sync._populate(
972
+ related_state,
973
+ self.mapper,
974
+ state,
975
+ self.parent,
976
+ self.prop.synchronize_pairs,
977
+ uowcommit,
978
+ self.passive_updates,
979
+ )
980
+
981
+ def _pks_changed(self, uowcommit, state):
982
+ return bool(state.key) and sync._source_modified(
983
+ uowcommit, state, self.mapper, self.prop.synchronize_pairs
984
+ )
985
+
986
+
987
+ class _ManyToManyDP(_DependencyProcessor):
988
+ def per_property_dependencies(
989
+ self,
990
+ uow,
991
+ parent_saves,
992
+ child_saves,
993
+ parent_deletes,
994
+ child_deletes,
995
+ after_save,
996
+ before_delete,
997
+ ):
998
+ uow.dependencies.update(
999
+ [
1000
+ (parent_saves, after_save),
1001
+ (child_saves, after_save),
1002
+ (after_save, child_deletes),
1003
+ # a rowswitch on the parent from deleted to saved
1004
+ # can make this one occur, as the "save" may remove
1005
+ # an element from the
1006
+ # "deleted" list before we have a chance to
1007
+ # process its child rows
1008
+ (before_delete, parent_saves),
1009
+ (before_delete, parent_deletes),
1010
+ (before_delete, child_deletes),
1011
+ (before_delete, child_saves),
1012
+ ]
1013
+ )
1014
+
1015
+ def per_state_dependencies(
1016
+ self,
1017
+ uow,
1018
+ save_parent,
1019
+ delete_parent,
1020
+ child_action,
1021
+ after_save,
1022
+ before_delete,
1023
+ isdelete,
1024
+ childisdelete,
1025
+ ):
1026
+ if not isdelete:
1027
+ if childisdelete:
1028
+ uow.dependencies.update(
1029
+ [(save_parent, after_save), (after_save, child_action)]
1030
+ )
1031
+ else:
1032
+ uow.dependencies.update(
1033
+ [(save_parent, after_save), (child_action, after_save)]
1034
+ )
1035
+ else:
1036
+ uow.dependencies.update(
1037
+ [(before_delete, child_action), (before_delete, delete_parent)]
1038
+ )
1039
+
1040
+ def presort_deletes(self, uowcommit, states):
1041
+ # TODO: no tests fail if this whole
1042
+ # thing is removed !!!!
1043
+ if not self.passive_deletes:
1044
+ # if no passive deletes, load history on
1045
+ # the collection, so that prop_has_changes()
1046
+ # returns True
1047
+ for state in states:
1048
+ uowcommit.get_attribute_history(
1049
+ state, self.key, self._passive_delete_flag
1050
+ )
1051
+
1052
+ def presort_saves(self, uowcommit, states):
1053
+ if not self.passive_updates:
1054
+ # if no passive updates, load history on
1055
+ # each collection where parent has changed PK,
1056
+ # so that prop_has_changes() returns True
1057
+ for state in states:
1058
+ if self._pks_changed(uowcommit, state):
1059
+ uowcommit.get_attribute_history(
1060
+ state, self.key, attributes.PASSIVE_OFF
1061
+ )
1062
+
1063
+ if not self.cascade.delete_orphan:
1064
+ return
1065
+
1066
+ # check for child items removed from the collection
1067
+ # if delete_orphan check is turned on.
1068
+ for state in states:
1069
+ history = uowcommit.get_attribute_history(
1070
+ state, self.key, attributes.PASSIVE_NO_INITIALIZE
1071
+ )
1072
+ if history:
1073
+ for child in history.deleted:
1074
+ if self.hasparent(child) is False:
1075
+ uowcommit.register_object(
1076
+ child,
1077
+ isdelete=True,
1078
+ operation="delete",
1079
+ prop=self.prop,
1080
+ )
1081
+ for c, m, st_, dct_ in self.mapper.cascade_iterator(
1082
+ "delete", child
1083
+ ):
1084
+ uowcommit.register_object(st_, isdelete=True)
1085
+
1086
+ def process_deletes(self, uowcommit, states):
1087
+ secondary_delete = []
1088
+ secondary_insert = []
1089
+ secondary_update = []
1090
+
1091
+ processed = self._get_reversed_processed_set(uowcommit)
1092
+ tmp = set()
1093
+ for state in states:
1094
+ # this history should be cached already, as
1095
+ # we loaded it in preprocess_deletes
1096
+ history = uowcommit.get_attribute_history(
1097
+ state, self.key, self._passive_delete_flag
1098
+ )
1099
+ if history:
1100
+ for child in history.non_added():
1101
+ if child is None or (
1102
+ processed is not None and (state, child) in processed
1103
+ ):
1104
+ continue
1105
+ associationrow = {}
1106
+ if not self._synchronize(
1107
+ state,
1108
+ child,
1109
+ associationrow,
1110
+ False,
1111
+ uowcommit,
1112
+ "delete",
1113
+ ):
1114
+ continue
1115
+ secondary_delete.append(associationrow)
1116
+
1117
+ tmp.update((c, state) for c in history.non_added())
1118
+
1119
+ if processed is not None:
1120
+ processed.update(tmp)
1121
+
1122
+ self._run_crud(
1123
+ uowcommit, secondary_insert, secondary_update, secondary_delete
1124
+ )
1125
+
1126
+ def process_saves(self, uowcommit, states):
1127
+ secondary_delete = []
1128
+ secondary_insert = []
1129
+ secondary_update = []
1130
+
1131
+ processed = self._get_reversed_processed_set(uowcommit)
1132
+ tmp = set()
1133
+
1134
+ for state in states:
1135
+ need_cascade_pks = not self.passive_updates and self._pks_changed(
1136
+ uowcommit, state
1137
+ )
1138
+ if need_cascade_pks:
1139
+ passive = (
1140
+ attributes.PASSIVE_OFF
1141
+ | attributes.INCLUDE_PENDING_MUTATIONS
1142
+ )
1143
+ else:
1144
+ passive = (
1145
+ attributes.PASSIVE_NO_INITIALIZE
1146
+ | attributes.INCLUDE_PENDING_MUTATIONS
1147
+ )
1148
+ history = uowcommit.get_attribute_history(state, self.key, passive)
1149
+ if history:
1150
+ for child in history.added:
1151
+ if processed is not None and (state, child) in processed:
1152
+ continue
1153
+ associationrow = {}
1154
+ if not self._synchronize(
1155
+ state, child, associationrow, False, uowcommit, "add"
1156
+ ):
1157
+ continue
1158
+ secondary_insert.append(associationrow)
1159
+ for child in history.deleted:
1160
+ if processed is not None and (state, child) in processed:
1161
+ continue
1162
+ associationrow = {}
1163
+ if not self._synchronize(
1164
+ state,
1165
+ child,
1166
+ associationrow,
1167
+ False,
1168
+ uowcommit,
1169
+ "delete",
1170
+ ):
1171
+ continue
1172
+ secondary_delete.append(associationrow)
1173
+
1174
+ tmp.update((c, state) for c in history.added + history.deleted)
1175
+
1176
+ if need_cascade_pks:
1177
+ for child in history.unchanged:
1178
+ associationrow = {}
1179
+ sync._update(
1180
+ state,
1181
+ self.parent,
1182
+ associationrow,
1183
+ "old_",
1184
+ self.prop.synchronize_pairs,
1185
+ )
1186
+ sync._update(
1187
+ child,
1188
+ self.mapper,
1189
+ associationrow,
1190
+ "old_",
1191
+ self.prop.secondary_synchronize_pairs,
1192
+ )
1193
+
1194
+ secondary_update.append(associationrow)
1195
+
1196
+ if processed is not None:
1197
+ processed.update(tmp)
1198
+
1199
+ self._run_crud(
1200
+ uowcommit, secondary_insert, secondary_update, secondary_delete
1201
+ )
1202
+
1203
+ def _run_crud(
1204
+ self, uowcommit, secondary_insert, secondary_update, secondary_delete
1205
+ ):
1206
+ connection = uowcommit.transaction.connection(self.mapper)
1207
+
1208
+ if secondary_delete:
1209
+ associationrow = secondary_delete[0]
1210
+ statement = self.secondary.delete().where(
1211
+ sql.and_(
1212
+ *[
1213
+ c == sql.bindparam(c.key, type_=c.type)
1214
+ for c in self.secondary.c
1215
+ if c.key in associationrow
1216
+ ]
1217
+ )
1218
+ )
1219
+ result = connection.execute(statement, secondary_delete)
1220
+
1221
+ if (
1222
+ result.supports_sane_multi_rowcount()
1223
+ ) and result.rowcount != len(secondary_delete):
1224
+ raise exc.StaleDataError(
1225
+ "DELETE statement on table '%s' expected to delete "
1226
+ "%d row(s); Only %d were matched."
1227
+ % (
1228
+ self.secondary.description,
1229
+ len(secondary_delete),
1230
+ result.rowcount,
1231
+ )
1232
+ )
1233
+
1234
+ if secondary_update:
1235
+ associationrow = secondary_update[0]
1236
+ statement = self.secondary.update().where(
1237
+ sql.and_(
1238
+ *[
1239
+ c == sql.bindparam("old_" + c.key, type_=c.type)
1240
+ for c in self.secondary.c
1241
+ if c.key in associationrow
1242
+ ]
1243
+ )
1244
+ )
1245
+ result = connection.execute(statement, secondary_update)
1246
+
1247
+ if (
1248
+ result.supports_sane_multi_rowcount()
1249
+ ) and result.rowcount != len(secondary_update):
1250
+ raise exc.StaleDataError(
1251
+ "UPDATE statement on table '%s' expected to update "
1252
+ "%d row(s); Only %d were matched."
1253
+ % (
1254
+ self.secondary.description,
1255
+ len(secondary_update),
1256
+ result.rowcount,
1257
+ )
1258
+ )
1259
+
1260
+ if secondary_insert:
1261
+ statement = self.secondary.insert()
1262
+ connection.execute(statement, secondary_insert)
1263
+
1264
+ def _synchronize(
1265
+ self, state, child, associationrow, clearkeys, uowcommit, operation
1266
+ ):
1267
+ # this checks for None if uselist=True
1268
+ self._verify_canload(child)
1269
+
1270
+ # but if uselist=False we get here. If child is None,
1271
+ # no association row can be generated, so return.
1272
+ if child is None:
1273
+ return False
1274
+
1275
+ if child is not None and not uowcommit.session._contains_state(child):
1276
+ if not child.deleted:
1277
+ util.warn(
1278
+ "Object of type %s not in session, %s "
1279
+ "operation along '%s' won't proceed"
1280
+ % (mapperutil.state_class_str(child), operation, self.prop)
1281
+ )
1282
+ return False
1283
+
1284
+ sync._populate_dict(
1285
+ state, self.parent, associationrow, self.prop.synchronize_pairs
1286
+ )
1287
+ sync._populate_dict(
1288
+ child,
1289
+ self.mapper,
1290
+ associationrow,
1291
+ self.prop.secondary_synchronize_pairs,
1292
+ )
1293
+
1294
+ return True
1295
+
1296
+ def _pks_changed(self, uowcommit, state):
1297
+ return sync._source_modified(
1298
+ uowcommit, state, self.parent, self.prop.synchronize_pairs
1299
+ )
1300
+
1301
+
1302
+ _direction_to_processor = {
1303
+ ONETOMANY: _OneToManyDP,
1304
+ MANYTOONE: _ManyToOneDP,
1305
+ MANYTOMANY: _ManyToManyDP,
1306
+ }