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