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